diff --git a/application/pom.xml b/application/pom.xml index b2e6fada28..ddd91e07e9 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.2.1-SNAPSHOT + 2.3.0 thingsboard application diff --git a/application/src/main/java/org/thingsboard/server/controller/AdminController.java b/application/src/main/java/org/thingsboard/server/controller/AdminController.java index edc452bed7..4575678ab5 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AdminController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AdminController.java @@ -28,6 +28,8 @@ import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.settings.AdminSettingsService; +import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.update.UpdateService; import org.thingsboard.server.service.update.model.UpdateMessage; @@ -49,6 +51,7 @@ public class AdminController extends BaseController { @ResponseBody public AdminSettings getAdminSettings(@PathVariable("key") String key) throws ThingsboardException { try { + accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); return checkNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, key)); } catch (Exception e) { throw handleException(e); @@ -60,6 +63,7 @@ public class AdminController extends BaseController { @ResponseBody public AdminSettings saveAdminSettings(@RequestBody AdminSettings adminSettings) throws ThingsboardException { try { + accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.WRITE); adminSettings = checkNotNull(adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, adminSettings)); if (adminSettings.getKey().equals("mail")) { mailService.updateMailConfiguration(); @@ -74,6 +78,7 @@ public class AdminController extends BaseController { @RequestMapping(value = "/settings/testMail", method = RequestMethod.POST) public void sendTestMail(@RequestBody AdminSettings adminSettings) throws ThingsboardException { try { + accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); adminSettings = checkNotNull(adminSettings); if (adminSettings.getKey().equals("mail")) { String email = getCurrentUser().getEmail(); diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java index 60381573b6..673d341681 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java @@ -41,6 +41,8 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.page.TimePageData; import org.thingsboard.server.common.data.page.TimePageLink; +import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; @RestController @RequestMapping("/api") @@ -55,7 +57,7 @@ public class AlarmController extends BaseController { checkParameter(ALARM_ID, strAlarmId); try { AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); - return checkAlarmId(alarmId); + return checkAlarmId(alarmId, Operation.READ); } catch (Exception e) { throw handleException(e); } @@ -68,7 +70,7 @@ public class AlarmController extends BaseController { checkParameter(ALARM_ID, strAlarmId); try { AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); - return checkAlarmInfoId(alarmId); + return checkAlarmInfoId(alarmId, Operation.READ); } catch (Exception e) { throw handleException(e); } @@ -80,6 +82,8 @@ public class AlarmController extends BaseController { public Alarm saveAlarm(@RequestBody Alarm alarm) throws ThingsboardException { try { alarm.setTenantId(getCurrentUser().getTenantId()); + Operation operation = alarm.getId() == null ? Operation.CREATE : Operation.WRITE; + accessControlService.checkPermission(getCurrentUser(), Resource.ALARM, operation, alarm.getId(), alarm); Alarm savedAlarm = checkNotNull(alarmService.createOrUpdateAlarm(alarm)); logEntityAction(savedAlarm.getId(), savedAlarm, getCurrentUser().getCustomerId(), @@ -112,7 +116,7 @@ public class AlarmController extends BaseController { checkParameter(ALARM_ID, strAlarmId); try { AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); - Alarm alarm = checkAlarmId(alarmId); + Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); alarmService.ackAlarm(getCurrentUser().getTenantId(), alarmId, System.currentTimeMillis()).get(); logEntityAction(alarmId, alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_ACK, null); } catch (Exception e) { @@ -127,7 +131,7 @@ public class AlarmController extends BaseController { checkParameter(ALARM_ID, strAlarmId); try { AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); - Alarm alarm = checkAlarmId(alarmId); + Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); alarmService.clearAlarm(getCurrentUser().getTenantId(), alarmId, null, System.currentTimeMillis()).get(); logEntityAction(alarmId, alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_CLEAR, null); } catch (Exception e) { @@ -159,7 +163,7 @@ public class AlarmController extends BaseController { throw new ThingsboardException("Invalid alarms search query: Both parameters 'searchStatus' " + "and 'status' can't be specified at the same time!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); } - checkEntityId(entityId); + checkEntityId(entityId, Operation.READ); try { TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(entityId, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get()); @@ -186,7 +190,7 @@ public class AlarmController extends BaseController { throw new ThingsboardException("Invalid alarms search query: Both parameters 'searchStatus' " + "and 'status' can't be specified at the same time!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); } - checkEntityId(entityId); + checkEntityId(entityId, Operation.READ); try { return alarmService.findHighestAlarmSeverity(getCurrentUser().getTenantId(), entityId, alarmSearchStatus, alarmStatus); } catch (Exception e) { diff --git a/application/src/main/java/org/thingsboard/server/controller/AssetController.java b/application/src/main/java/org/thingsboard/server/controller/AssetController.java index d1b7fee371..cb4a989d60 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AssetController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AssetController.java @@ -43,6 +43,8 @@ import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; import java.util.ArrayList; import java.util.List; @@ -61,7 +63,7 @@ public class AssetController extends BaseController { checkParameter(ASSET_ID, strAssetId); try { AssetId assetId = new AssetId(toUUID(strAssetId)); - return checkAssetId(assetId); + return checkAssetId(assetId, Operation.READ); } catch (Exception e) { throw handleException(e); } @@ -73,15 +75,12 @@ public class AssetController extends BaseController { public Asset saveAsset(@RequestBody Asset asset) throws ThingsboardException { try { asset.setTenantId(getCurrentUser().getTenantId()); - if (getCurrentUser().getAuthority() == Authority.CUSTOMER_USER) { - if (asset.getId() == null || asset.getId().isNullUid() || - asset.getCustomerId() == null || asset.getCustomerId().isNullUid()) { - throw new ThingsboardException("You don't have permission to perform this operation!", - ThingsboardErrorCode.PERMISSION_DENIED); - } else { - checkCustomerId(asset.getCustomerId()); - } - } + + Operation operation = asset.getId() == null ? Operation.CREATE : Operation.WRITE; + + accessControlService.checkPermission(getCurrentUser(), Resource.ASSET, operation, + asset.getId(), asset); + Asset savedAsset = checkNotNull(assetService.saveAsset(asset)); logEntityAction(savedAsset.getId(), savedAsset, @@ -103,7 +102,7 @@ public class AssetController extends BaseController { checkParameter(ASSET_ID, strAssetId); try { AssetId assetId = new AssetId(toUUID(strAssetId)); - Asset asset = checkAssetId(assetId); + Asset asset = checkAssetId(assetId, Operation.DELETE); assetService.deleteAsset(getTenantId(), assetId); logEntityAction(assetId, asset, @@ -128,10 +127,10 @@ public class AssetController extends BaseController { checkParameter(ASSET_ID, strAssetId); try { CustomerId customerId = new CustomerId(toUUID(strCustomerId)); - Customer customer = checkCustomerId(customerId); + Customer customer = checkCustomerId(customerId, Operation.READ); AssetId assetId = new AssetId(toUUID(strAssetId)); - checkAssetId(assetId); + checkAssetId(assetId, Operation.ASSIGN_TO_CUSTOMER); Asset savedAsset = checkNotNull(assetService.assignAssetToCustomer(getTenantId(), assetId, customerId)); @@ -157,12 +156,12 @@ public class AssetController extends BaseController { checkParameter(ASSET_ID, strAssetId); try { AssetId assetId = new AssetId(toUUID(strAssetId)); - Asset asset = checkAssetId(assetId); + Asset asset = checkAssetId(assetId, Operation.UNASSIGN_FROM_CUSTOMER); if (asset.getCustomerId() == null || asset.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { throw new IncorrectParameterException("Asset isn't assigned to any customer!"); } - Customer customer = checkCustomerId(asset.getCustomerId()); + Customer customer = checkCustomerId(asset.getCustomerId(), Operation.READ); Asset savedAsset = checkNotNull(assetService.unassignAssetFromCustomer(getTenantId(), assetId)); @@ -188,7 +187,7 @@ public class AssetController extends BaseController { checkParameter(ASSET_ID, strAssetId); try { AssetId assetId = new AssetId(toUUID(strAssetId)); - Asset asset = checkAssetId(assetId); + Asset asset = checkAssetId(assetId, Operation.ASSIGN_TO_CUSTOMER); Customer publicCustomer = customerService.findOrCreatePublicCustomer(asset.getTenantId()); Asset savedAsset = checkNotNull(assetService.assignAssetToCustomer(getTenantId(), assetId, publicCustomer.getId())); @@ -256,7 +255,7 @@ public class AssetController extends BaseController { try { TenantId tenantId = getCurrentUser().getTenantId(); CustomerId customerId = new CustomerId(toUUID(strCustomerId)); - checkCustomerId(customerId); + checkCustomerId(customerId, Operation.READ); TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); if (type != null && type.trim().length()>0) { return checkNotNull(assetService.findAssetsByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink)); @@ -301,12 +300,12 @@ public class AssetController extends BaseController { checkNotNull(query); checkNotNull(query.getParameters()); checkNotNull(query.getAssetTypes()); - checkEntityId(query.getParameters().getEntityId()); + checkEntityId(query.getParameters().getEntityId(), Operation.READ); try { List assets = checkNotNull(assetService.findAssetsByQuery(getTenantId(), query).get()); assets = assets.stream().filter(asset -> { try { - checkAsset(asset); + accessControlService.checkPermission(getCurrentUser(), Resource.ASSET, Operation.READ, asset.getId(), asset); return true; } catch (ThingsboardException e) { return false; diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 6055b1bde2..1d879ec99f 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -44,6 +44,7 @@ import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.plugin.ComponentDescriptor; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.widget.WidgetType; import org.thingsboard.server.common.data.widget.WidgetsBundle; @@ -74,6 +75,9 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService; import org.thingsboard.server.exception.ThingsboardErrorResponseHandler; import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.security.permission.AccessControlService; +import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.state.DeviceStateService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; @@ -98,6 +102,9 @@ public abstract class BaseController { @Autowired private ThingsboardErrorResponseHandler errorResponseHandler; + @Autowired + protected AccessControlService accessControlService; + @Autowired protected TenantService tenantService; @@ -252,13 +259,15 @@ public abstract class BaseController { } } - void checkTenantId(TenantId tenantId) throws ThingsboardException { - validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - SecurityUser authUser = getCurrentUser(); - if (authUser.getAuthority() != Authority.SYS_ADMIN && - (authUser.getTenantId() == null || !authUser.getTenantId().equals(tenantId))) { - throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION, - ThingsboardErrorCode.PERMISSION_DENIED); + Tenant checkTenantId(TenantId tenantId, Operation operation) throws ThingsboardException { + try { + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + Tenant tenant = tenantService.findTenantById(tenantId); + checkNotNull(tenant); + accessControlService.checkPermission(getCurrentUser(), Resource.TENANT, operation, tenantId, tenant); + return tenant; + } catch (Exception e) { + throw handleException(e, false); } } @@ -266,80 +275,61 @@ public abstract class BaseController { return getCurrentUser().getTenantId(); } - Customer checkCustomerId(CustomerId customerId) throws ThingsboardException { + Customer checkCustomerId(CustomerId customerId, Operation operation) throws ThingsboardException { try { - SecurityUser authUser = getCurrentUser(); - if (authUser.getAuthority() == Authority.SYS_ADMIN || - (authUser.getAuthority() != Authority.TENANT_ADMIN && - (authUser.getCustomerId() == null || !authUser.getCustomerId().equals(customerId)))) { - throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION, - ThingsboardErrorCode.PERMISSION_DENIED); - } - if (customerId != null && !customerId.isNullUid()) { - Customer customer = customerService.findCustomerById(authUser.getTenantId(), customerId); - checkCustomer(customer); - return customer; - } else { - return null; - } + validateId(customerId, "Incorrect customerId " + customerId); + Customer customer = customerService.findCustomerById(getTenantId(), customerId); + checkNotNull(customer); + accessControlService.checkPermission(getCurrentUser(), Resource.CUSTOMER, operation, customerId, customer); + return customer; } catch (Exception e) { throw handleException(e, false); } } - private void checkCustomer(Customer customer) throws ThingsboardException { - checkNotNull(customer); - checkTenantId(customer.getTenantId()); - } - - User checkUserId(UserId userId) throws ThingsboardException { + User checkUserId(UserId userId, Operation operation) throws ThingsboardException { try { validateId(userId, "Incorrect userId " + userId); User user = userService.findUserById(getCurrentUser().getTenantId(), userId); - checkUser(user); + checkNotNull(user); + accessControlService.checkPermission(getCurrentUser(), Resource.USER, operation, userId, user); return user; } catch (Exception e) { throw handleException(e, false); } } - private void checkUser(User user) throws ThingsboardException { - checkNotNull(user); - checkTenantId(user.getTenantId()); - if (user.getAuthority() == Authority.CUSTOMER_USER) { - checkCustomerId(user.getCustomerId()); - } - } - - protected void checkEntityId(EntityId entityId) throws ThingsboardException { + protected void checkEntityId(EntityId entityId, Operation operation) throws ThingsboardException { try { checkNotNull(entityId); validateId(entityId.getId(), "Incorrect entityId " + entityId); - SecurityUser authUser = getCurrentUser(); switch (entityId.getEntityType()) { case DEVICE: - checkDevice(deviceService.findDeviceById(authUser.getTenantId(), new DeviceId(entityId.getId()))); + checkDeviceId(new DeviceId(entityId.getId()), operation); return; case CUSTOMER: - checkCustomerId(new CustomerId(entityId.getId())); + checkCustomerId(new CustomerId(entityId.getId()), operation); return; case TENANT: - checkTenantId(new TenantId(entityId.getId())); + checkTenantId(new TenantId(entityId.getId()), operation); return; case RULE_CHAIN: - checkRuleChain(new RuleChainId(entityId.getId())); + checkRuleChain(new RuleChainId(entityId.getId()), operation); + return; + case RULE_NODE: + checkRuleNode(new RuleNodeId(entityId.getId()), operation); return; case ASSET: - checkAsset(assetService.findAssetById(authUser.getTenantId(), new AssetId(entityId.getId()))); + checkAssetId(new AssetId(entityId.getId()), operation); return; case DASHBOARD: - checkDashboardId(new DashboardId(entityId.getId())); + checkDashboardId(new DashboardId(entityId.getId()), operation); return; case USER: - checkUserId(new UserId(entityId.getId())); + checkUserId(new UserId(entityId.getId()), operation); return; case ENTITY_VIEW: - checkEntityViewId(new EntityViewId(entityId.getId())); + checkEntityViewId(new EntityViewId(entityId.getId()), operation); return; default: throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType()); @@ -349,160 +339,114 @@ public abstract class BaseController { } } - Device checkDeviceId(DeviceId deviceId) throws ThingsboardException { + Device checkDeviceId(DeviceId deviceId, Operation operation) throws ThingsboardException { try { validateId(deviceId, "Incorrect deviceId " + deviceId); Device device = deviceService.findDeviceById(getCurrentUser().getTenantId(), deviceId); - checkDevice(device); + checkNotNull(device); + accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, operation, deviceId, device); return device; } catch (Exception e) { throw handleException(e, false); } } - protected void checkDevice(Device device) throws ThingsboardException { - checkNotNull(device); - checkTenantId(device.getTenantId()); - checkCustomerId(device.getCustomerId()); - } - - protected EntityView checkEntityViewId(EntityViewId entityViewId) throws ThingsboardException { + protected EntityView checkEntityViewId(EntityViewId entityViewId, Operation operation) throws ThingsboardException { try { validateId(entityViewId, "Incorrect entityViewId " + entityViewId); EntityView entityView = entityViewService.findEntityViewById(getCurrentUser().getTenantId(), entityViewId); - checkEntityView(entityView); + checkNotNull(entityView); + accessControlService.checkPermission(getCurrentUser(), Resource.ENTITY_VIEW, operation, entityViewId, entityView); return entityView; } catch (Exception e) { throw handleException(e, false); } } - protected void checkEntityView(EntityView entityView) throws ThingsboardException { - checkNotNull(entityView); - checkTenantId(entityView.getTenantId()); - checkCustomerId(entityView.getCustomerId()); - } - - Asset checkAssetId(AssetId assetId) throws ThingsboardException { + Asset checkAssetId(AssetId assetId, Operation operation) throws ThingsboardException { try { validateId(assetId, "Incorrect assetId " + assetId); Asset asset = assetService.findAssetById(getCurrentUser().getTenantId(), assetId); - checkAsset(asset); + checkNotNull(asset); + accessControlService.checkPermission(getCurrentUser(), Resource.ASSET, operation, assetId, asset); return asset; } catch (Exception e) { throw handleException(e, false); } } - protected void checkAsset(Asset asset) throws ThingsboardException { - checkNotNull(asset); - checkTenantId(asset.getTenantId()); - checkCustomerId(asset.getCustomerId()); - } - - Alarm checkAlarmId(AlarmId alarmId) throws ThingsboardException { + Alarm checkAlarmId(AlarmId alarmId, Operation operation) throws ThingsboardException { try { validateId(alarmId, "Incorrect alarmId " + alarmId); Alarm alarm = alarmService.findAlarmByIdAsync(getCurrentUser().getTenantId(), alarmId).get(); - checkAlarm(alarm); + checkNotNull(alarm); + accessControlService.checkPermission(getCurrentUser(), Resource.ALARM, operation, alarmId, alarm); return alarm; } catch (Exception e) { throw handleException(e, false); } } - AlarmInfo checkAlarmInfoId(AlarmId alarmId) throws ThingsboardException { + AlarmInfo checkAlarmInfoId(AlarmId alarmId, Operation operation) throws ThingsboardException { try { validateId(alarmId, "Incorrect alarmId " + alarmId); AlarmInfo alarmInfo = alarmService.findAlarmInfoByIdAsync(getCurrentUser().getTenantId(), alarmId).get(); - checkAlarm(alarmInfo); + checkNotNull(alarmInfo); + accessControlService.checkPermission(getCurrentUser(), Resource.ALARM, operation, alarmId, alarmInfo); return alarmInfo; } catch (Exception e) { throw handleException(e, false); } } - protected void checkAlarm(Alarm alarm) throws ThingsboardException { - checkNotNull(alarm); - checkTenantId(alarm.getTenantId()); - } - - WidgetsBundle checkWidgetsBundleId(WidgetsBundleId widgetsBundleId, boolean modify) throws ThingsboardException { + WidgetsBundle checkWidgetsBundleId(WidgetsBundleId widgetsBundleId, Operation operation) throws ThingsboardException { try { validateId(widgetsBundleId, "Incorrect widgetsBundleId " + widgetsBundleId); WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleById(getCurrentUser().getTenantId(), widgetsBundleId); - checkWidgetsBundle(widgetsBundle, modify); + checkNotNull(widgetsBundle); + accessControlService.checkPermission(getCurrentUser(), Resource.WIDGETS_BUNDLE, operation, widgetsBundleId, widgetsBundle); return widgetsBundle; } catch (Exception e) { throw handleException(e, false); } } - private void checkWidgetsBundle(WidgetsBundle widgetsBundle, boolean modify) throws ThingsboardException { - checkNotNull(widgetsBundle); - if (widgetsBundle.getTenantId() != null && !widgetsBundle.getTenantId().getId().equals(ModelConstants.NULL_UUID)) { - checkTenantId(widgetsBundle.getTenantId()); - } else if (modify && getCurrentUser().getAuthority() != Authority.SYS_ADMIN) { - throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION, - ThingsboardErrorCode.PERMISSION_DENIED); - } - } - - WidgetType checkWidgetTypeId(WidgetTypeId widgetTypeId, boolean modify) throws ThingsboardException { + WidgetType checkWidgetTypeId(WidgetTypeId widgetTypeId, Operation operation) throws ThingsboardException { try { validateId(widgetTypeId, "Incorrect widgetTypeId " + widgetTypeId); WidgetType widgetType = widgetTypeService.findWidgetTypeById(getCurrentUser().getTenantId(), widgetTypeId); - checkWidgetType(widgetType, modify); + checkNotNull(widgetType); + accessControlService.checkPermission(getCurrentUser(), Resource.WIDGET_TYPE, operation, widgetTypeId, widgetType); return widgetType; } catch (Exception e) { throw handleException(e, false); } } - void checkWidgetType(WidgetType widgetType, boolean modify) throws ThingsboardException { - checkNotNull(widgetType); - if (widgetType.getTenantId() != null && !widgetType.getTenantId().getId().equals(ModelConstants.NULL_UUID)) { - checkTenantId(widgetType.getTenantId()); - } else if (modify && getCurrentUser().getAuthority() != Authority.SYS_ADMIN) { - throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION, - ThingsboardErrorCode.PERMISSION_DENIED); - } - } - - Dashboard checkDashboardId(DashboardId dashboardId) throws ThingsboardException { + Dashboard checkDashboardId(DashboardId dashboardId, Operation operation) throws ThingsboardException { try { validateId(dashboardId, "Incorrect dashboardId " + dashboardId); Dashboard dashboard = dashboardService.findDashboardById(getCurrentUser().getTenantId(), dashboardId); - checkDashboard(dashboard); + checkNotNull(dashboard); + accessControlService.checkPermission(getCurrentUser(), Resource.DASHBOARD, operation, dashboardId, dashboard); return dashboard; } catch (Exception e) { throw handleException(e, false); } } - DashboardInfo checkDashboardInfoId(DashboardId dashboardId) throws ThingsboardException { + DashboardInfo checkDashboardInfoId(DashboardId dashboardId, Operation operation) throws ThingsboardException { try { validateId(dashboardId, "Incorrect dashboardId " + dashboardId); DashboardInfo dashboardInfo = dashboardService.findDashboardInfoById(getCurrentUser().getTenantId(), dashboardId); - checkDashboard(dashboardInfo); + checkNotNull(dashboardInfo); + accessControlService.checkPermission(getCurrentUser(), Resource.DASHBOARD, operation, dashboardId, dashboardInfo); return dashboardInfo; } catch (Exception e) { throw handleException(e, false); } } - private void checkDashboard(DashboardInfo dashboard) throws ThingsboardException { - checkNotNull(dashboard); - checkTenantId(dashboard.getTenantId()); - SecurityUser authUser = getCurrentUser(); - if (authUser.getAuthority() == Authority.CUSTOMER_USER) { - if (!dashboard.isAssignedToCustomer(authUser.getCustomerId())) { - throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION, - ThingsboardErrorCode.PERMISSION_DENIED); - } - } - } - ComponentDescriptor checkComponentDescriptorByClazz(String clazz) throws ThingsboardException { try { log.debug("[{}] Lookup component descriptor", clazz); @@ -530,24 +474,22 @@ public abstract class BaseController { } } - protected RuleChain checkRuleChain(RuleChainId ruleChainId) throws ThingsboardException { - checkNotNull(ruleChainId); - return checkRuleChain(ruleChainService.findRuleChainById(getCurrentUser().getTenantId(), ruleChainId)); - } - - protected RuleChain checkRuleChain(RuleChain ruleChain) throws ThingsboardException { + protected RuleChain checkRuleChain(RuleChainId ruleChainId, Operation operation) throws ThingsboardException { + validateId(ruleChainId, "Incorrect ruleChainId " + ruleChainId); + RuleChain ruleChain = ruleChainService.findRuleChainById(getCurrentUser().getTenantId(), ruleChainId); checkNotNull(ruleChain); - SecurityUser authUser = getCurrentUser(); - TenantId tenantId = ruleChain.getTenantId(); - validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - if (authUser.getAuthority() != Authority.TENANT_ADMIN || - !authUser.getTenantId().equals(tenantId)) { - throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION, - ThingsboardErrorCode.PERMISSION_DENIED); - } + accessControlService.checkPermission(getCurrentUser(), Resource.RULE_CHAIN, operation, ruleChainId, ruleChain); return ruleChain; } + protected RuleNode checkRuleNode(RuleNodeId ruleNodeId, Operation operation) throws ThingsboardException { + validateId(ruleNodeId, "Incorrect ruleNodeId " + ruleNodeId); + RuleNode ruleNode = ruleChainService.findRuleNodeById(getTenantId(), ruleNodeId); + checkNotNull(ruleNode); + checkRuleChain(ruleNode.getRuleChainId(), operation); + return ruleNode; + } + protected String constructBaseUrl(HttpServletRequest request) { String scheme = request.getScheme(); diff --git a/application/src/main/java/org/thingsboard/server/controller/CustomerController.java b/application/src/main/java/org/thingsboard/server/controller/CustomerController.java index 44ebea4351..7b7cfeee87 100644 --- a/application/src/main/java/org/thingsboard/server/controller/CustomerController.java +++ b/application/src/main/java/org/thingsboard/server/controller/CustomerController.java @@ -36,6 +36,8 @@ import org.thingsboard.server.common.data.id.CustomerId; 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.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; @RestController @RequestMapping("/api") @@ -51,7 +53,7 @@ public class CustomerController extends BaseController { checkParameter(CUSTOMER_ID, strCustomerId); try { CustomerId customerId = new CustomerId(toUUID(strCustomerId)); - return checkCustomerId(customerId); + return checkCustomerId(customerId, Operation.READ); } catch (Exception e) { throw handleException(e); } @@ -64,7 +66,7 @@ public class CustomerController extends BaseController { checkParameter(CUSTOMER_ID, strCustomerId); try { CustomerId customerId = new CustomerId(toUUID(strCustomerId)); - Customer customer = checkCustomerId(customerId); + Customer customer = checkCustomerId(customerId, Operation.READ); ObjectMapper objectMapper = new ObjectMapper(); ObjectNode infoObject = objectMapper.createObjectNode(); infoObject.put("title", customer.getTitle()); @@ -82,7 +84,7 @@ public class CustomerController extends BaseController { checkParameter(CUSTOMER_ID, strCustomerId); try { CustomerId customerId = new CustomerId(toUUID(strCustomerId)); - Customer customer = checkCustomerId(customerId); + Customer customer = checkCustomerId(customerId, Operation.READ); return customer.getTitle(); } catch (Exception e) { throw handleException(e); @@ -95,6 +97,10 @@ public class CustomerController extends BaseController { public Customer saveCustomer(@RequestBody Customer customer) throws ThingsboardException { try { customer.setTenantId(getCurrentUser().getTenantId()); + + Operation operation = customer.getId() == null ? Operation.CREATE : Operation.WRITE; + accessControlService.checkPermission(getCurrentUser(), Resource.CUSTOMER, operation, customer.getId(), customer); + Customer savedCustomer = checkNotNull(customerService.saveCustomer(customer)); logEntityAction(savedCustomer.getId(), savedCustomer, @@ -118,7 +124,7 @@ public class CustomerController extends BaseController { checkParameter(CUSTOMER_ID, strCustomerId); try { CustomerId customerId = new CustomerId(toUUID(strCustomerId)); - Customer customer = checkCustomerId(customerId); + Customer customer = checkCustomerId(customerId, Operation.DELETE); customerService.deleteCustomer(getTenantId(), customerId); logEntityAction(customerId, customer, diff --git a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java index 44fbe96310..e47e2d1a06 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java @@ -41,6 +41,8 @@ import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TimePageData; import org.thingsboard.server.common.data.page.TimePageLink; +import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; import java.util.HashSet; import java.util.Set; @@ -76,7 +78,7 @@ public class DashboardController extends BaseController { checkParameter(DASHBOARD_ID, strDashboardId); try { DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); - return checkDashboardInfoId(dashboardId); + return checkDashboardInfoId(dashboardId, Operation.READ); } catch (Exception e) { throw handleException(e); } @@ -89,7 +91,7 @@ public class DashboardController extends BaseController { checkParameter(DASHBOARD_ID, strDashboardId); try { DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); - return checkDashboardId(dashboardId); + return checkDashboardId(dashboardId, Operation.READ); } catch (Exception e) { throw handleException(e); } @@ -101,6 +103,12 @@ public class DashboardController extends BaseController { public Dashboard saveDashboard(@RequestBody Dashboard dashboard) throws ThingsboardException { try { dashboard.setTenantId(getCurrentUser().getTenantId()); + + Operation operation = dashboard.getId() == null ? Operation.CREATE : Operation.WRITE; + + accessControlService.checkPermission(getCurrentUser(), Resource.DASHBOARD, operation, + dashboard.getId(), dashboard); + Dashboard savedDashboard = checkNotNull(dashboardService.saveDashboard(dashboard)); logEntityAction(savedDashboard.getId(), savedDashboard, @@ -123,7 +131,7 @@ public class DashboardController extends BaseController { checkParameter(DASHBOARD_ID, strDashboardId); try { DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); - Dashboard dashboard = checkDashboardId(dashboardId); + Dashboard dashboard = checkDashboardId(dashboardId, Operation.DELETE); dashboardService.deleteDashboard(getCurrentUser().getTenantId(), dashboardId); logEntityAction(dashboardId, dashboard, @@ -150,10 +158,10 @@ public class DashboardController extends BaseController { checkParameter(DASHBOARD_ID, strDashboardId); try { CustomerId customerId = new CustomerId(toUUID(strCustomerId)); - Customer customer = checkCustomerId(customerId); + Customer customer = checkCustomerId(customerId, Operation.READ); DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); - checkDashboardId(dashboardId); + checkDashboardId(dashboardId, Operation.ASSIGN_TO_CUSTOMER); Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(getCurrentUser().getTenantId(), dashboardId, customerId)); @@ -182,9 +190,9 @@ public class DashboardController extends BaseController { checkParameter(DASHBOARD_ID, strDashboardId); try { CustomerId customerId = new CustomerId(toUUID(strCustomerId)); - Customer customer = checkCustomerId(customerId); + Customer customer = checkCustomerId(customerId, Operation.READ); DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); - Dashboard dashboard = checkDashboardId(dashboardId); + Dashboard dashboard = checkDashboardId(dashboardId, Operation.UNASSIGN_FROM_CUSTOMER); Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(getCurrentUser().getTenantId(), dashboardId, customerId)); @@ -211,7 +219,7 @@ public class DashboardController extends BaseController { checkParameter(DASHBOARD_ID, strDashboardId); try { DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); - Dashboard dashboard = checkDashboardId(dashboardId); + Dashboard dashboard = checkDashboardId(dashboardId, Operation.ASSIGN_TO_CUSTOMER); Set customerIds = new HashSet<>(); if (strCustomerIds != null) { @@ -276,7 +284,7 @@ public class DashboardController extends BaseController { checkParameter(DASHBOARD_ID, strDashboardId); try { DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); - Dashboard dashboard = checkDashboardId(dashboardId); + Dashboard dashboard = checkDashboardId(dashboardId, Operation.ASSIGN_TO_CUSTOMER); Set customerIds = new HashSet<>(); if (strCustomerIds != null) { @@ -319,7 +327,7 @@ public class DashboardController extends BaseController { checkParameter(DASHBOARD_ID, strDashboardId); try { DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); - Dashboard dashboard = checkDashboardId(dashboardId); + Dashboard dashboard = checkDashboardId(dashboardId, Operation.UNASSIGN_FROM_CUSTOMER); Set customerIds = new HashSet<>(); if (strCustomerIds != null) { @@ -362,7 +370,7 @@ public class DashboardController extends BaseController { checkParameter(DASHBOARD_ID, strDashboardId); try { DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); - Dashboard dashboard = checkDashboardId(dashboardId); + Dashboard dashboard = checkDashboardId(dashboardId, Operation.ASSIGN_TO_CUSTOMER); Customer publicCustomer = customerService.findOrCreatePublicCustomer(dashboard.getTenantId()); Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(getCurrentUser().getTenantId(), dashboardId, publicCustomer.getId())); @@ -388,7 +396,7 @@ public class DashboardController extends BaseController { checkParameter(DASHBOARD_ID, strDashboardId); try { DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); - Dashboard dashboard = checkDashboardId(dashboardId); + Dashboard dashboard = checkDashboardId(dashboardId, Operation.UNASSIGN_FROM_CUSTOMER); Customer publicCustomer = customerService.findOrCreatePublicCustomer(dashboard.getTenantId()); Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(getCurrentUser().getTenantId(), dashboardId, publicCustomer.getId())); @@ -419,7 +427,7 @@ public class DashboardController extends BaseController { @RequestParam(required = false) String textOffset) throws ThingsboardException { try { TenantId tenantId = new TenantId(toUUID(strTenantId)); - checkTenantId(tenantId); + checkTenantId(tenantId, Operation.READ); TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); return checkNotNull(dashboardService.findDashboardsByTenantId(tenantId, pageLink)); } catch (Exception e) { @@ -458,7 +466,7 @@ public class DashboardController extends BaseController { try { TenantId tenantId = getCurrentUser().getTenantId(); CustomerId customerId = new CustomerId(toUUID(strCustomerId)); - checkCustomerId(customerId); + checkCustomerId(customerId, Operation.READ); TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); return checkNotNull(dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get()); } catch (Exception e) { diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index 17ebbc930f..0fd5ccfdfa 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -44,6 +44,8 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; import java.util.ArrayList; import java.util.List; @@ -62,7 +64,7 @@ public class DeviceController extends BaseController { checkParameter(DEVICE_ID, strDeviceId); try { DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); - return checkDeviceId(deviceId); + return checkDeviceId(deviceId, Operation.READ); } catch (Exception e) { throw handleException(e); } @@ -74,15 +76,12 @@ public class DeviceController extends BaseController { public Device saveDevice(@RequestBody Device device) throws ThingsboardException { try { device.setTenantId(getCurrentUser().getTenantId()); - if (getCurrentUser().getAuthority() == Authority.CUSTOMER_USER) { - if (device.getId() == null || device.getId().isNullUid() || - device.getCustomerId() == null || device.getCustomerId().isNullUid()) { - throw new ThingsboardException("You don't have permission to perform this operation!", - ThingsboardErrorCode.PERMISSION_DENIED); - } else { - checkCustomerId(device.getCustomerId()); - } - } + + Operation operation = device.getId() == null ? Operation.CREATE : Operation.WRITE; + + accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, operation, + device.getId(), device); + Device savedDevice = checkNotNull(deviceService.saveDevice(device)); actorService @@ -116,7 +115,7 @@ public class DeviceController extends BaseController { checkParameter(DEVICE_ID, strDeviceId); try { DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); - Device device = checkDeviceId(deviceId); + Device device = checkDeviceId(deviceId, Operation.DELETE); deviceService.deleteDevice(getCurrentUser().getTenantId(), deviceId); logEntityAction(deviceId, device, @@ -142,10 +141,10 @@ public class DeviceController extends BaseController { checkParameter(DEVICE_ID, strDeviceId); try { CustomerId customerId = new CustomerId(toUUID(strCustomerId)); - Customer customer = checkCustomerId(customerId); + Customer customer = checkCustomerId(customerId, Operation.READ); DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); - checkDeviceId(deviceId); + checkDeviceId(deviceId, Operation.ASSIGN_TO_CUSTOMER); Device savedDevice = checkNotNull(deviceService.assignDeviceToCustomer(getCurrentUser().getTenantId(), deviceId, customerId)); @@ -169,11 +168,11 @@ public class DeviceController extends BaseController { checkParameter(DEVICE_ID, strDeviceId); try { DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); - Device device = checkDeviceId(deviceId); + Device device = checkDeviceId(deviceId, Operation.UNASSIGN_FROM_CUSTOMER); if (device.getCustomerId() == null || device.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { throw new IncorrectParameterException("Device isn't assigned to any customer!"); } - Customer customer = checkCustomerId(device.getCustomerId()); + Customer customer = checkCustomerId(device.getCustomerId(), Operation.READ); Device savedDevice = checkNotNull(deviceService.unassignDeviceFromCustomer(getCurrentUser().getTenantId(), deviceId)); @@ -197,7 +196,7 @@ public class DeviceController extends BaseController { checkParameter(DEVICE_ID, strDeviceId); try { DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); - Device device = checkDeviceId(deviceId); + Device device = checkDeviceId(deviceId, Operation.ASSIGN_TO_CUSTOMER); Customer publicCustomer = customerService.findOrCreatePublicCustomer(device.getTenantId()); Device savedDevice = checkNotNull(deviceService.assignDeviceToCustomer(getCurrentUser().getTenantId(), deviceId, publicCustomer.getId())); @@ -221,7 +220,7 @@ public class DeviceController extends BaseController { checkParameter(DEVICE_ID, strDeviceId); try { DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); - Device device = checkDeviceId(deviceId); + Device device = checkDeviceId(deviceId, Operation.READ_CREDENTIALS); DeviceCredentials deviceCredentials = checkNotNull(deviceCredentialsService.findDeviceCredentialsByDeviceId(getCurrentUser().getTenantId(), deviceId)); logEntityAction(deviceId, device, device.getCustomerId(), @@ -241,7 +240,7 @@ public class DeviceController extends BaseController { public DeviceCredentials saveDeviceCredentials(@RequestBody DeviceCredentials deviceCredentials) throws ThingsboardException { checkNotNull(deviceCredentials); try { - Device device = checkDeviceId(deviceCredentials.getDeviceId()); + Device device = checkDeviceId(deviceCredentials.getDeviceId(), Operation.WRITE_CREDENTIALS); DeviceCredentials result = checkNotNull(deviceCredentialsService.updateDeviceCredentials(getCurrentUser().getTenantId(), deviceCredentials)); actorService.onCredentialsUpdate(getCurrentUser().getTenantId(), deviceCredentials.getDeviceId()); logEntityAction(device.getId(), device, @@ -305,7 +304,7 @@ public class DeviceController extends BaseController { try { TenantId tenantId = getCurrentUser().getTenantId(); CustomerId customerId = new CustomerId(toUUID(strCustomerId)); - checkCustomerId(customerId); + checkCustomerId(customerId, Operation.READ); TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); if (type != null && type.trim().length() > 0) { return checkNotNull(deviceService.findDevicesByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink)); @@ -350,12 +349,12 @@ public class DeviceController extends BaseController { checkNotNull(query); checkNotNull(query.getParameters()); checkNotNull(query.getDeviceTypes()); - checkEntityId(query.getParameters().getEntityId()); + checkEntityId(query.getParameters().getEntityId(), Operation.READ); try { List devices = checkNotNull(deviceService.findDevicesByQuery(getCurrentUser().getTenantId(), query).get()); devices = devices.stream().filter(device -> { try { - checkDevice(device); + accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, Operation.READ, device.getId(), device); return true; } catch (ThingsboardException e) { return false; diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java index ec95781afb..175347afcf 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java @@ -35,6 +35,7 @@ import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelationInfo; import org.thingsboard.server.common.data.relation.EntityRelationsQuery; import org.thingsboard.server.common.data.relation.RelationTypeGroup; +import org.thingsboard.server.service.security.permission.Operation; import java.util.List; @@ -55,8 +56,8 @@ public class EntityRelationController extends BaseController { public void saveRelation(@RequestBody EntityRelation relation) throws ThingsboardException { try { checkNotNull(relation); - checkEntityId(relation.getFrom()); - checkEntityId(relation.getTo()); + checkEntityId(relation.getFrom(), Operation.WRITE); + checkEntityId(relation.getTo(), Operation.WRITE); if (relation.getTypeGroup() == null) { relation.setTypeGroup(RelationTypeGroup.COMMON); } @@ -89,8 +90,8 @@ public class EntityRelationController extends BaseController { checkParameter(TO_TYPE, strToType); EntityId fromId = EntityIdFactory.getByTypeAndId(strFromType, strFromId); EntityId toId = EntityIdFactory.getByTypeAndId(strToType, strToId); - checkEntityId(fromId); - checkEntityId(toId); + checkEntityId(fromId, Operation.WRITE); + checkEntityId(toId, Operation.WRITE); RelationTypeGroup relationTypeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON); EntityRelation relation = new EntityRelation(fromId, toId, strRelationType, relationTypeGroup); try { @@ -119,7 +120,7 @@ public class EntityRelationController extends BaseController { checkParameter("entityId", strId); checkParameter("entityType", strType); EntityId entityId = EntityIdFactory.getByTypeAndId(strType, strId); - checkEntityId(entityId); + checkEntityId(entityId, Operation.WRITE); try { relationService.deleteEntityRelations(getTenantId(), entityId); logEntityAction(entityId, null, getCurrentUser().getCustomerId(), ActionType.RELATIONS_DELETED, null); @@ -145,8 +146,8 @@ public class EntityRelationController extends BaseController { checkParameter(TO_TYPE, strToType); EntityId fromId = EntityIdFactory.getByTypeAndId(strFromType, strFromId); EntityId toId = EntityIdFactory.getByTypeAndId(strToType, strToId); - checkEntityId(fromId); - checkEntityId(toId); + checkEntityId(fromId, Operation.READ); + checkEntityId(toId, Operation.READ); RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON); return checkNotNull(relationService.getRelation(getTenantId(), fromId, toId, strRelationType, typeGroup)); } catch (Exception e) { @@ -163,7 +164,7 @@ public class EntityRelationController extends BaseController { checkParameter(FROM_ID, strFromId); checkParameter(FROM_TYPE, strFromType); EntityId entityId = EntityIdFactory.getByTypeAndId(strFromType, strFromId); - checkEntityId(entityId); + checkEntityId(entityId, Operation.READ); RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON); try { return checkNotNull(relationService.findByFrom(getTenantId(), entityId, typeGroup)); @@ -181,7 +182,7 @@ public class EntityRelationController extends BaseController { checkParameter(FROM_ID, strFromId); checkParameter(FROM_TYPE, strFromType); EntityId entityId = EntityIdFactory.getByTypeAndId(strFromType, strFromId); - checkEntityId(entityId); + checkEntityId(entityId, Operation.READ); RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON); try { return checkNotNull(relationService.findInfoByFrom(getTenantId(), entityId, typeGroup).get()); @@ -201,7 +202,7 @@ public class EntityRelationController extends BaseController { checkParameter(FROM_TYPE, strFromType); checkParameter(RELATION_TYPE, strRelationType); EntityId entityId = EntityIdFactory.getByTypeAndId(strFromType, strFromId); - checkEntityId(entityId); + checkEntityId(entityId, Operation.READ); RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON); try { return checkNotNull(relationService.findByFromAndType(getTenantId(), entityId, strRelationType, typeGroup)); @@ -219,7 +220,7 @@ public class EntityRelationController extends BaseController { checkParameter(TO_ID, strToId); checkParameter(TO_TYPE, strToType); EntityId entityId = EntityIdFactory.getByTypeAndId(strToType, strToId); - checkEntityId(entityId); + checkEntityId(entityId, Operation.READ); RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON); try { return checkNotNull(relationService.findByTo(getTenantId(), entityId, typeGroup)); @@ -237,7 +238,7 @@ public class EntityRelationController extends BaseController { checkParameter(TO_ID, strToId); checkParameter(TO_TYPE, strToType); EntityId entityId = EntityIdFactory.getByTypeAndId(strToType, strToId); - checkEntityId(entityId); + checkEntityId(entityId, Operation.READ); RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON); try { return checkNotNull(relationService.findInfoByTo(getTenantId(), entityId, typeGroup).get()); @@ -257,7 +258,7 @@ public class EntityRelationController extends BaseController { checkParameter(TO_TYPE, strToType); checkParameter(RELATION_TYPE, strRelationType); EntityId entityId = EntityIdFactory.getByTypeAndId(strToType, strToId); - checkEntityId(entityId); + checkEntityId(entityId, Operation.READ); RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON); try { return checkNotNull(relationService.findByToAndType(getTenantId(), entityId, strRelationType, typeGroup)); @@ -273,7 +274,7 @@ public class EntityRelationController extends BaseController { checkNotNull(query); checkNotNull(query.getParameters()); checkNotNull(query.getFilters()); - checkEntityId(query.getParameters().getEntityId()); + checkEntityId(query.getParameters().getEntityId(), Operation.READ); try { return checkNotNull(relationService.findByQuery(getTenantId(), query).get()); } catch (Exception e) { @@ -288,7 +289,7 @@ public class EntityRelationController extends BaseController { checkNotNull(query); checkNotNull(query.getParameters()); checkNotNull(query.getFilters()); - checkEntityId(query.getParameters().getEntityId()); + checkEntityId(query.getParameters().getEntityId(), Operation.READ); try { return checkNotNull(relationService.findInfoByQuery(getTenantId(), query).get()); } catch (Exception e) { 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 65df81f29e..b0d9896e4b 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java @@ -48,6 +48,8 @@ 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 org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; import javax.annotation.Nullable; import java.util.ArrayList; @@ -74,7 +76,7 @@ public class EntityViewController extends BaseController { public EntityView getEntityViewById(@PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException { checkParameter(ENTITY_VIEW_ID, strEntityViewId); try { - return checkEntityViewId(new EntityViewId(toUUID(strEntityViewId))); + return checkEntityViewId(new EntityViewId(toUUID(strEntityViewId)), Operation.READ); } catch (Exception e) { throw handleException(e); } @@ -86,6 +88,12 @@ public class EntityViewController extends BaseController { public EntityView saveEntityView(@RequestBody EntityView entityView) throws ThingsboardException { try { entityView.setTenantId(getCurrentUser().getTenantId()); + + Operation operation = entityView.getId() == null ? Operation.CREATE : Operation.WRITE; + + accessControlService.checkPermission(getCurrentUser(), Resource.ENTITY_VIEW, operation, + entityView.getId(), entityView); + EntityView savedEntityView = checkNotNull(entityViewService.saveEntityView(entityView)); List>> futures = new ArrayList<>(); if (savedEntityView.getKeys() != null && savedEntityView.getKeys().getAttributes() != null) { @@ -168,7 +176,7 @@ public class EntityViewController extends BaseController { checkParameter(ENTITY_VIEW_ID, strEntityViewId); try { EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId)); - EntityView entityView = checkEntityViewId(entityViewId); + EntityView entityView = checkEntityViewId(entityViewId, Operation.DELETE); entityViewService.deleteEntityView(getTenantId(), entityViewId); logEntityAction(entityViewId, entityView, entityView.getCustomerId(), ActionType.DELETED, null, strEntityViewId); @@ -203,10 +211,10 @@ public class EntityViewController extends BaseController { checkParameter(ENTITY_VIEW_ID, strEntityViewId); try { CustomerId customerId = new CustomerId(toUUID(strCustomerId)); - Customer customer = checkCustomerId(customerId); + Customer customer = checkCustomerId(customerId, Operation.READ); EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId)); - checkEntityViewId(entityViewId); + checkEntityViewId(entityViewId, Operation.ASSIGN_TO_CUSTOMER); EntityView savedEntityView = checkNotNull(entityViewService.assignEntityViewToCustomer(getTenantId(), entityViewId, customerId)); logEntityAction(entityViewId, savedEntityView, @@ -228,11 +236,11 @@ public class EntityViewController extends BaseController { checkParameter(ENTITY_VIEW_ID, strEntityViewId); try { EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId)); - EntityView entityView = checkEntityViewId(entityViewId); + EntityView entityView = checkEntityViewId(entityViewId, Operation.UNASSIGN_FROM_CUSTOMER); 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()); + Customer customer = checkCustomerId(entityView.getCustomerId(), Operation.READ); EntityView savedEntityView = checkNotNull(entityViewService.unassignEntityViewFromCustomer(getTenantId(), entityViewId)); logEntityAction(entityViewId, entityView, entityView.getCustomerId(), @@ -261,7 +269,7 @@ public class EntityViewController extends BaseController { try { TenantId tenantId = getCurrentUser().getTenantId(); CustomerId customerId = new CustomerId(toUUID(strCustomerId)); - checkCustomerId(customerId); + checkCustomerId(customerId, Operation.READ); TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); if (type != null && type.trim().length() > 0) { return checkNotNull(entityViewService.findEntityViewsByTenantIdAndCustomerIdAndType(tenantId, customerId, pageLink, type)); @@ -303,12 +311,12 @@ public class EntityViewController extends BaseController { checkNotNull(query); checkNotNull(query.getParameters()); checkNotNull(query.getEntityViewTypes()); - checkEntityId(query.getParameters().getEntityId()); + checkEntityId(query.getParameters().getEntityId(), Operation.READ); try { List entityViews = checkNotNull(entityViewService.findEntityViewsByQuery(getTenantId(), query).get()); entityViews = entityViews.stream().filter(entityView -> { try { - checkEntityView(entityView); + accessControlService.checkPermission(getCurrentUser(), Resource.ENTITY_VIEW, Operation.READ, entityView.getId(), entityView); return true; } catch (ThingsboardException e) { return false; @@ -341,7 +349,7 @@ public class EntityViewController extends BaseController { checkParameter(ENTITY_VIEW_ID, strEntityViewId); try { EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId)); - EntityView entityView = checkEntityViewId(entityViewId); + EntityView entityView = checkEntityViewId(entityViewId, Operation.ASSIGN_TO_CUSTOMER); Customer publicCustomer = customerService.findOrCreatePublicCustomer(entityView.getTenantId()); EntityView savedEntityView = checkNotNull(entityViewService.assignEntityViewToCustomer(getCurrentUser().getTenantId(), entityViewId, publicCustomer.getId())); diff --git a/application/src/main/java/org/thingsboard/server/controller/EventController.java b/application/src/main/java/org/thingsboard/server/controller/EventController.java index 048d957648..8d08179409 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EventController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EventController.java @@ -26,12 +26,15 @@ import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.Event; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.TimePageData; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; @RestController @RequestMapping("/api") @@ -58,13 +61,12 @@ public class EventController extends BaseController { checkParameter("EntityType", strEntityType); try { TenantId tenantId = new TenantId(toUUID(strTenantId)); - if (!tenantId.getId().equals(ModelConstants.NULL_UUID) && - !tenantId.equals(getCurrentUser().getTenantId())) { - throw new ThingsboardException("You don't have permission to perform this operation!", - ThingsboardErrorCode.PERMISSION_DENIED); - } + + EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId); + checkEntityId(entityId, Operation.READ); + TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); - return checkNotNull(eventService.findEvents(tenantId, EntityIdFactory.getByTypeAndId(strEntityType, strEntityId), eventType, pageLink)); + return checkNotNull(eventService.findEvents(tenantId, entityId, eventType, pageLink)); } catch (Exception e) { throw handleException(e); } @@ -87,13 +89,12 @@ public class EventController extends BaseController { checkParameter("EntityType", strEntityType); try { TenantId tenantId = new TenantId(toUUID(strTenantId)); - if (!tenantId.getId().equals(ModelConstants.NULL_UUID) && - !tenantId.equals(getCurrentUser().getTenantId())) { - throw new ThingsboardException("You don't have permission to perform this operation!", - ThingsboardErrorCode.PERMISSION_DENIED); - } + + EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId); + checkEntityId(entityId, Operation.READ); + TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); - return checkNotNull(eventService.findEvents(tenantId, EntityIdFactory.getByTypeAndId(strEntityType, strEntityId), pageLink)); + return checkNotNull(eventService.findEvents(tenantId, entityId, pageLink)); } catch (Exception e) { throw handleException(e); } diff --git a/application/src/main/java/org/thingsboard/server/controller/RpcController.java b/application/src/main/java/org/thingsboard/server/controller/RpcController.java index 14bd1a54bc..3b624e9b94 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RpcController.java +++ b/application/src/main/java/org/thingsboard/server/controller/RpcController.java @@ -47,6 +47,7 @@ import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; import org.thingsboard.server.service.rpc.LocalRequestMetaData; import org.thingsboard.server.service.security.AccessValidator; import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.telemetry.exception.ToErrorResponseEntity; import javax.annotation.Nullable; @@ -118,7 +119,7 @@ public class RpcController extends BaseController { final DeferredResult response = new DeferredResult<>(); long timeout = System.currentTimeMillis() + (cmd.getTimeout() != null ? cmd.getTimeout() : DEFAULT_TIMEOUT); ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(cmd.getMethodName(), cmd.getRequestData()); - accessValidator.validate(currentUser, deviceId, new HttpValidationCallback(response, new FutureCallback>() { + accessValidator.validate(currentUser, Operation.RPC_CALL, deviceId, new HttpValidationCallback(response, new FutureCallback>() { @Override public void onSuccess(@Nullable DeferredResult result) { ToDeviceRpcRequest rpcRequest = new ToDeviceRpcRequest(UUID.randomUUID(), diff --git a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java index 25874fe89b..2b0387049d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java +++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java @@ -47,15 +47,20 @@ import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.service.script.JsInvokeService; import org.thingsboard.server.service.script.RuleNodeJsScriptEngine; +import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; @Slf4j @RestController @@ -80,7 +85,7 @@ public class RuleChainController extends BaseController { checkParameter(RULE_CHAIN_ID, strRuleChainId); try { RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); - return checkRuleChain(ruleChainId); + return checkRuleChain(ruleChainId, Operation.READ); } catch (Exception e) { throw handleException(e); } @@ -93,7 +98,7 @@ public class RuleChainController extends BaseController { checkParameter(RULE_CHAIN_ID, strRuleChainId); try { RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); - checkRuleChain(ruleChainId); + checkRuleChain(ruleChainId, Operation.READ); return ruleChainService.loadRuleChainMetaData(getTenantId(), ruleChainId); } catch (Exception e) { throw handleException(e); @@ -108,6 +113,12 @@ public class RuleChainController extends BaseController { try { boolean created = ruleChain.getId() == null; ruleChain.setTenantId(getCurrentUser().getTenantId()); + + Operation operation = created ? Operation.CREATE : Operation.WRITE; + + accessControlService.checkPermission(getCurrentUser(), Resource.RULE_CHAIN, operation, + ruleChain.getId(), ruleChain); + RuleChain savedRuleChain = checkNotNull(ruleChainService.saveRuleChain(ruleChain)); actorService.onEntityStateChange(ruleChain.getTenantId(), savedRuleChain.getId(), @@ -134,7 +145,7 @@ public class RuleChainController extends BaseController { checkParameter(RULE_CHAIN_ID, strRuleChainId); try { RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); - RuleChain ruleChain = checkRuleChain(ruleChainId); + RuleChain ruleChain = checkRuleChain(ruleChainId, Operation.WRITE); TenantId tenantId = getCurrentUser().getTenantId(); RuleChain previousRootRuleChain = ruleChainService.getRootTenantRuleChain(tenantId); if (ruleChainService.setRootRuleChain(getTenantId(), ruleChainId)) { @@ -171,7 +182,7 @@ public class RuleChainController extends BaseController { @ResponseBody public RuleChainMetaData saveRuleChainMetaData(@RequestBody RuleChainMetaData ruleChainMetaData) throws ThingsboardException { try { - RuleChain ruleChain = checkRuleChain(ruleChainMetaData.getRuleChainId()); + RuleChain ruleChain = checkRuleChain(ruleChainMetaData.getRuleChainId(), Operation.WRITE); RuleChainMetaData savedRuleChainMetaData = checkNotNull(ruleChainService.saveRuleChainMetaData(getTenantId(), ruleChainMetaData)); actorService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.UPDATED); @@ -214,10 +225,19 @@ public class RuleChainController extends BaseController { checkParameter(RULE_CHAIN_ID, strRuleChainId); try { RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); - RuleChain ruleChain = checkRuleChain(ruleChainId); + RuleChain ruleChain = checkRuleChain(ruleChainId, Operation.DELETE); + + List referencingRuleNodes = ruleChainService.getReferencingRuleChainNodes(getTenantId(), ruleChainId); + + Set referencingRuleChainIds = referencingRuleNodes.stream().map(RuleNode::getRuleChainId).collect(Collectors.toSet()); ruleChainService.deleteRuleChainById(getTenantId(), ruleChainId); + referencingRuleChainIds.remove(ruleChain.getId()); + + referencingRuleChainIds.forEach(referencingRuleChainId -> + actorService.onEntityStateChange(ruleChain.getTenantId(), referencingRuleChainId, ComponentLifecycleEvent.UPDATED)); + actorService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.DELETED); logEntityAction(ruleChainId, ruleChain, @@ -240,6 +260,7 @@ public class RuleChainController extends BaseController { checkParameter(RULE_NODE_ID, strRuleNodeId); try { RuleNodeId ruleNodeId = new RuleNodeId(toUUID(strRuleNodeId)); + checkRuleNode(ruleNodeId, Operation.READ); TenantId tenantId = getCurrentUser().getTenantId(); List events = eventService.findLatestEvents(tenantId, ruleNodeId, DataConstants.DEBUG_RULE_NODE, 2); JsonNode result = null; diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java index 8c7ac95ac5..d46e6475c0 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java @@ -67,6 +67,7 @@ import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.service.security.AccessValidator; import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.telemetry.AttributeData; import org.thingsboard.server.service.telemetry.TsData; import org.thingsboard.server.service.telemetry.exception.InvalidParametersException; @@ -122,7 +123,7 @@ public class TelemetryController extends BaseController { @ResponseBody public DeferredResult getAttributeKeys( @PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr) throws ThingsboardException { - return accessValidator.validateEntityAndCallback(getCurrentUser(), entityType, entityIdStr, this::getAttributeKeysCallback); + return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, this::getAttributeKeysCallback); } @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @@ -131,7 +132,7 @@ public class TelemetryController extends BaseController { public DeferredResult getAttributeKeysByScope( @PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr , @PathVariable("scope") String scope) throws ThingsboardException { - return accessValidator.validateEntityAndCallback(getCurrentUser(), entityType, entityIdStr, + return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, (result, tenantId, entityId) -> getAttributeKeysCallback(result, tenantId, entityId, scope)); } @@ -142,7 +143,7 @@ public class TelemetryController extends BaseController { @PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr, @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException { SecurityUser user = getCurrentUser(); - return accessValidator.validateEntityAndCallback(getCurrentUser(), entityType, entityIdStr, + return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, (result, tenantId, entityId) -> getAttributeValuesCallback(result, user, entityId, null, keysStr)); } @@ -154,7 +155,7 @@ public class TelemetryController extends BaseController { @PathVariable("scope") String scope, @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException { SecurityUser user = getCurrentUser(); - return accessValidator.validateEntityAndCallback(getCurrentUser(), entityType, entityIdStr, + return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, (result, tenantId, entityId) -> getAttributeValuesCallback(result, user, entityId, scope, keysStr)); } @@ -163,7 +164,7 @@ public class TelemetryController extends BaseController { @ResponseBody public DeferredResult getTimeseriesKeys( @PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr) throws ThingsboardException { - return accessValidator.validateEntityAndCallback(getCurrentUser(), entityType, entityIdStr, + return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr, (result, tenantId, entityId) -> Futures.addCallback(tsService.findAllLatest(tenantId, entityId), getTsKeysToResponseCallback(result))); } @@ -175,7 +176,7 @@ public class TelemetryController extends BaseController { @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException { SecurityUser user = getCurrentUser(); - return accessValidator.validateEntityAndCallback(getCurrentUser(), entityType, entityIdStr, + return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr, (result, tenantId, entityId) -> getLatestTimeseriesValuesCallback(result, user, entityId, keysStr)); } @@ -192,7 +193,7 @@ public class TelemetryController extends BaseController { @RequestParam(name = "limit", defaultValue = "100") Integer limit, @RequestParam(name = "agg", defaultValue = "NONE") String aggStr ) throws ThingsboardException { - return accessValidator.validateEntityAndCallback(getCurrentUser(), entityType, entityIdStr, + return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr, (result, tenantId, entityId) -> { // If interval is 0, convert this to a NONE aggregation, which is probably what the user really wanted Aggregation agg = interval == 0L ? Aggregation.valueOf(Aggregation.NONE.name()) : Aggregation.valueOf(aggStr); @@ -289,7 +290,7 @@ public class TelemetryController extends BaseController { } } - return accessValidator.validateEntityAndCallback(user, entityIdStr, (result, tenantId, entityId) -> { + return accessValidator.validateEntityAndCallback(user, Operation.WRITE_TELEMETRY, entityIdStr, (result, tenantId, entityId) -> { List deleteTsKvQueries = new ArrayList<>(); for (String key : keys) { deleteTsKvQueries.add(new BaseDeleteTsKvQuery(key, deleteFromTs, deleteToTs, rewriteLatestIfDeleted)); @@ -342,7 +343,7 @@ public class TelemetryController extends BaseController { if (DataConstants.SERVER_SCOPE.equals(scope) || DataConstants.SHARED_SCOPE.equals(scope) || DataConstants.CLIENT_SCOPE.equals(scope)) { - return accessValidator.validateEntityAndCallback(getCurrentUser(), entityIdStr, (result, tenantId, entityId) -> { + return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_ATTRIBUTES, entityIdStr, (result, tenantId, entityId) -> { ListenableFuture> future = attributesService.removeAll(user.getTenantId(), entityId, scope, keys); Futures.addCallback(future, new FutureCallback>() { @Override @@ -381,7 +382,7 @@ public class TelemetryController extends BaseController { return getImmediateDeferredResult("No attributes data found in request body!", HttpStatus.BAD_REQUEST); } SecurityUser user = getCurrentUser(); - return accessValidator.validateEntityAndCallback(getCurrentUser(), entityIdSrc, (result, tenantId, entityId) -> { + return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_ATTRIBUTES, entityIdSrc, (result, tenantId, entityId) -> { tsSubService.saveAndNotify(tenantId, entityId, scope, attributes, new FutureCallback() { @Override public void onSuccess(@Nullable Void tmp) { @@ -430,7 +431,7 @@ public class TelemetryController extends BaseController { return getImmediateDeferredResult("No timeseries data found in request body!", HttpStatus.BAD_REQUEST); } SecurityUser user = getCurrentUser(); - return accessValidator.validateEntityAndCallback(getCurrentUser(), entityIdSrc, (result, tenantId, entityId) -> { + return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_TELEMETRY, entityIdSrc, (result, tenantId, entityId) -> { tsSubService.saveAndNotify(tenantId, entityId, entries, ttl, new FutureCallback() { @Override public void onSuccess(@Nullable Void tmp) { diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantController.java b/application/src/main/java/org/thingsboard/server/controller/TenantController.java index 86dfccb082..e7e13e18bb 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TenantController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TenantController.java @@ -35,6 +35,8 @@ import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.service.install.InstallScripts; +import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; @RestController @RequestMapping("/api") @@ -54,7 +56,7 @@ public class TenantController extends BaseController { checkParameter("tenantId", strTenantId); try { TenantId tenantId = new TenantId(toUUID(strTenantId)); - checkTenantId(tenantId); + checkTenantId(tenantId, Operation.READ); return checkNotNull(tenantService.findTenantById(tenantId)); } catch (Exception e) { throw handleException(e); @@ -67,6 +69,12 @@ public class TenantController extends BaseController { public Tenant saveTenant(@RequestBody Tenant tenant) throws ThingsboardException { try { boolean newTenant = tenant.getId() == null; + + Operation operation = newTenant ? Operation.CREATE : Operation.WRITE; + + accessControlService.checkPermission(getCurrentUser(), Resource.TENANT, operation, + tenant.getId(), tenant); + tenant = checkNotNull(tenantService.saveTenant(tenant)); if (newTenant) { installScripts.createDefaultRuleChains(tenant.getId()); @@ -84,6 +92,7 @@ public class TenantController extends BaseController { checkParameter("tenantId", strTenantId); try { TenantId tenantId = new TenantId(toUUID(strTenantId)); + checkTenantId(tenantId, Operation.DELETE); tenantService.deleteTenant(tenantId); actorService.onEntityStateChange(tenantId, tenantId, ComponentLifecycleEvent.DELETED); diff --git a/application/src/main/java/org/thingsboard/server/controller/UserController.java b/application/src/main/java/org/thingsboard/server/controller/UserController.java index 0f4ef231a0..b6a8d9da06 100644 --- a/application/src/main/java/org/thingsboard/server/controller/UserController.java +++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java @@ -49,6 +49,8 @@ import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.UserPrincipal; import org.thingsboard.server.service.security.model.token.JwtToken; import org.thingsboard.server.service.security.model.token.JwtTokenFactory; +import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; import javax.servlet.http.HttpServletRequest; @@ -81,12 +83,7 @@ public class UserController extends BaseController { checkParameter(USER_ID, strUserId); try { UserId userId = new UserId(toUUID(strUserId)); - SecurityUser authUser = getCurrentUser(); - if (authUser.getAuthority() == Authority.CUSTOMER_USER && !authUser.getId().equals(userId)) { - throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION, - ThingsboardErrorCode.PERMISSION_DENIED); - } - return checkUserId(userId); + return checkUserId(userId, Operation.READ); } catch (Exception e) { throw handleException(e); } @@ -105,14 +102,13 @@ public class UserController extends BaseController { public JsonNode getUserToken(@PathVariable(USER_ID) String strUserId) throws ThingsboardException { checkParameter(USER_ID, strUserId); try { - UserId userId = new UserId(toUUID(strUserId)); - SecurityUser authUser = getCurrentUser(); - User user = userService.findUserById(authUser.getTenantId(), userId); - if (!userTokenAccessEnabled || (authUser.getAuthority() == Authority.SYS_ADMIN && user.getAuthority() != Authority.TENANT_ADMIN) - || (authUser.getAuthority() == Authority.TENANT_ADMIN && !authUser.getTenantId().equals(user.getTenantId()))) { + if (!userTokenAccessEnabled) { throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION, ThingsboardErrorCode.PERMISSION_DENIED); } + UserId userId = new UserId(toUUID(strUserId)); + SecurityUser authUser = getCurrentUser(); + User user = checkUserId(userId, Operation.READ); UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail()); UserCredentials credentials = userService.findUserCredentialsByUserId(authUser.getTenantId(), userId); SecurityUser securityUser = new SecurityUser(user, credentials.isEnabled(), principal); @@ -135,17 +131,20 @@ public class UserController extends BaseController { @RequestParam(required = false, defaultValue = "true") boolean sendActivationMail, HttpServletRequest request) throws ThingsboardException { try { - SecurityUser authUser = getCurrentUser(); - if (authUser.getAuthority() == Authority.CUSTOMER_USER && !authUser.getId().equals(user.getId())) { - throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION, - ThingsboardErrorCode.PERMISSION_DENIED); - } - boolean sendEmail = user.getId() == null && sendActivationMail; + if (getCurrentUser().getAuthority() == Authority.TENANT_ADMIN) { user.setTenantId(getCurrentUser().getTenantId()); } + + Operation operation = user.getId() == null ? Operation.CREATE : Operation.WRITE; + + accessControlService.checkPermission(getCurrentUser(), Resource.USER, operation, + user.getId(), user); + + boolean sendEmail = user.getId() == null && sendActivationMail; User savedUser = checkNotNull(userService.saveUser(user)); if (sendEmail) { + SecurityUser authUser = getCurrentUser(); UserCredentials userCredentials = userService.findUserCredentialsByUserId(authUser.getTenantId(), savedUser.getId()); String baseUrl = constructBaseUrl(request); String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl, @@ -181,6 +180,10 @@ public class UserController extends BaseController { HttpServletRequest request) throws ThingsboardException { try { User user = checkNotNull(userService.findUserByEmail(getCurrentUser().getTenantId(), email)); + + accessControlService.checkPermission(getCurrentUser(), Resource.USER, Operation.READ, + user.getId(), user); + UserCredentials userCredentials = userService.findUserCredentialsByUserId(getCurrentUser().getTenantId(), user.getId()); if (!userCredentials.isEnabled()) { String baseUrl = constructBaseUrl(request); @@ -204,13 +207,9 @@ public class UserController extends BaseController { checkParameter(USER_ID, strUserId); try { UserId userId = new UserId(toUUID(strUserId)); + User user = checkUserId(userId, Operation.READ); SecurityUser authUser = getCurrentUser(); - if (authUser.getAuthority() == Authority.CUSTOMER_USER && !authUser.getId().equals(userId)) { - throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION, - ThingsboardErrorCode.PERMISSION_DENIED); - } - User user = checkUserId(userId); - UserCredentials userCredentials = userService.findUserCredentialsByUserId(getCurrentUser().getTenantId(), user.getId()); + UserCredentials userCredentials = userService.findUserCredentialsByUserId(authUser.getTenantId(), user.getId()); if (!userCredentials.isEnabled()) { String baseUrl = constructBaseUrl(request); String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl, @@ -231,7 +230,7 @@ public class UserController extends BaseController { checkParameter(USER_ID, strUserId); try { UserId userId = new UserId(toUUID(strUserId)); - User user = checkUserId(userId); + User user = checkUserId(userId, Operation.DELETE); userService.deleteUser(getCurrentUser().getTenantId(), userId); logEntityAction(userId, user, @@ -278,7 +277,7 @@ public class UserController extends BaseController { checkParameter("customerId", strCustomerId); try { CustomerId customerId = new CustomerId(toUUID(strCustomerId)); - checkCustomerId(customerId); + checkCustomerId(customerId, Operation.READ); TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); TenantId tenantId = getCurrentUser().getTenantId(); return checkNotNull(userService.findCustomerUsers(tenantId, customerId, pageLink)); diff --git a/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java b/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java index b1fd668088..25c76f3607 100644 --- a/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java +++ b/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java @@ -31,6 +31,8 @@ import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.widget.WidgetType; import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; import java.util.List; @@ -45,7 +47,7 @@ public class WidgetTypeController extends BaseController { checkParameter("widgetTypeId", strWidgetTypeId); try { WidgetTypeId widgetTypeId = new WidgetTypeId(toUUID(strWidgetTypeId)); - return checkWidgetTypeId(widgetTypeId, false); + return checkWidgetTypeId(widgetTypeId, Operation.READ); } catch (Exception e) { throw handleException(e); } @@ -57,10 +59,16 @@ public class WidgetTypeController extends BaseController { public WidgetType saveWidgetType(@RequestBody WidgetType widgetType) throws ThingsboardException { try { if (getCurrentUser().getAuthority() == Authority.SYS_ADMIN) { - widgetType.setTenantId(new TenantId(ModelConstants.NULL_UUID)); + widgetType.setTenantId(TenantId.SYS_TENANT_ID); } else { widgetType.setTenantId(getCurrentUser().getTenantId()); } + + Operation operation = widgetType.getId() == null ? Operation.CREATE : Operation.WRITE; + + accessControlService.checkPermission(getCurrentUser(), Resource.WIDGET_TYPE, operation, + widgetType.getId(), widgetType); + return checkNotNull(widgetTypeService.saveWidgetType(widgetType)); } catch (Exception e) { throw handleException(e); @@ -74,7 +82,7 @@ public class WidgetTypeController extends BaseController { checkParameter("widgetTypeId", strWidgetTypeId); try { WidgetTypeId widgetTypeId = new WidgetTypeId(toUUID(strWidgetTypeId)); - checkWidgetTypeId(widgetTypeId, true); + checkWidgetTypeId(widgetTypeId, Operation.DELETE); widgetTypeService.deleteWidgetType(getCurrentUser().getTenantId(), widgetTypeId); } catch (Exception e) { throw handleException(e); @@ -90,7 +98,7 @@ public class WidgetTypeController extends BaseController { try { TenantId tenantId; if (isSystem) { - tenantId = new TenantId(ModelConstants.NULL_UUID); + tenantId = TenantId.SYS_TENANT_ID; } else { tenantId = getCurrentUser().getTenantId(); } @@ -115,7 +123,8 @@ public class WidgetTypeController extends BaseController { tenantId = getCurrentUser().getTenantId(); } WidgetType widgetType = widgetTypeService.findWidgetTypeByTenantIdBundleAliasAndAlias(tenantId, bundleAlias, alias); - checkWidgetType(widgetType, false); + checkNotNull(widgetType); + accessControlService.checkPermission(getCurrentUser(), Resource.WIDGET_TYPE, Operation.READ, widgetType.getId(), widgetType); return widgetType; } catch (Exception e) { throw handleException(e); diff --git a/application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java b/application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java index 5eb2074680..3c8265e7a7 100644 --- a/application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java +++ b/application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java @@ -33,6 +33,8 @@ import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; import java.util.List; @@ -47,7 +49,7 @@ public class WidgetsBundleController extends BaseController { checkParameter("widgetsBundleId", strWidgetsBundleId); try { WidgetsBundleId widgetsBundleId = new WidgetsBundleId(toUUID(strWidgetsBundleId)); - return checkWidgetsBundleId(widgetsBundleId, false); + return checkWidgetsBundleId(widgetsBundleId, Operation.READ); } catch (Exception e) { throw handleException(e); } @@ -59,10 +61,16 @@ public class WidgetsBundleController extends BaseController { public WidgetsBundle saveWidgetsBundle(@RequestBody WidgetsBundle widgetsBundle) throws ThingsboardException { try { if (getCurrentUser().getAuthority() == Authority.SYS_ADMIN) { - widgetsBundle.setTenantId(new TenantId(ModelConstants.NULL_UUID)); + widgetsBundle.setTenantId(TenantId.SYS_TENANT_ID); } else { widgetsBundle.setTenantId(getCurrentUser().getTenantId()); } + + Operation operation = widgetsBundle.getId() == null ? Operation.CREATE : Operation.WRITE; + + accessControlService.checkPermission(getCurrentUser(), Resource.WIDGETS_BUNDLE, operation, + widgetsBundle.getId(), widgetsBundle); + return checkNotNull(widgetsBundleService.saveWidgetsBundle(widgetsBundle)); } catch (Exception e) { throw handleException(e); @@ -76,7 +84,7 @@ public class WidgetsBundleController extends BaseController { checkParameter("widgetsBundleId", strWidgetsBundleId); try { WidgetsBundleId widgetsBundleId = new WidgetsBundleId(toUUID(strWidgetsBundleId)); - checkWidgetsBundleId(widgetsBundleId, true); + checkWidgetsBundleId(widgetsBundleId, Operation.DELETE); widgetsBundleService.deleteWidgetsBundle(getTenantId(), widgetsBundleId); } catch (Exception e) { throw handleException(e); diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index bf7e5458b5..21a8314163 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -23,7 +23,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; import org.thingsboard.server.service.component.ComponentDiscoveryService; -import org.thingsboard.server.service.install.DataUpdateService; +import org.thingsboard.server.service.install.update.DataUpdateService; import org.thingsboard.server.service.install.DatabaseUpgradeService; import org.thingsboard.server.service.install.EntityDatabaseSchemaService; import org.thingsboard.server.service.install.SystemDataLoaderService; @@ -106,6 +106,9 @@ public class ThingsboardInstallService { databaseUpgradeService.upgradeDatabase("2.1.3"); + case "2.2.0": + log.info("Upgrading ThingsBoard from version 2.2.0 to 2.3.0 ..."); + log.info("Updating system data..."); systemDataLoaderService.deleteSystemWidgetBundle("charts"); @@ -121,7 +124,6 @@ public class ThingsboardInstallService { systemDataLoaderService.deleteSystemWidgetBundle("input_widgets"); systemDataLoaderService.loadSystemWidgets(); - break; default: throw new RuntimeException("Unable to upgrade ThingsBoard, unsupported fromVersion: " + upgradeFromVersion); diff --git a/application/src/main/java/org/thingsboard/server/service/install/DataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DataUpdateService.java similarity index 92% rename from application/src/main/java/org/thingsboard/server/service/install/DataUpdateService.java rename to application/src/main/java/org/thingsboard/server/service/install/update/DataUpdateService.java index 79a6206029..f67d0782b4 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DataUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DataUpdateService.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.install; +package org.thingsboard.server.service.install.update; public interface DataUpdateService { diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java similarity index 76% rename from application/src/main/java/org/thingsboard/server/service/install/DefaultDataUpdateService.java rename to application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java index ee15e22b64..9396ae7c3d 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultDataUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.install; +package org.thingsboard.server.service.install.update; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.tenant.TenantService; +import org.thingsboard.server.service.install.InstallScripts; @Service @Profile("install") @@ -75,29 +76,4 @@ public class DefaultDataUpdateService implements DataUpdateService { } }; - public abstract class PaginatedUpdater> { - - private static final int DEFAULT_LIMIT = 100; - - public void updateEntities(I id) { - TextPageLink pageLink = new TextPageLink(DEFAULT_LIMIT); - boolean hasNext = true; - while (hasNext) { - TextPageData entities = findEntities(id, pageLink); - for (D entity : entities.getData()) { - updateEntity(entity); - } - hasNext = entities.hasNext(); - if (hasNext) { - pageLink = entities.getNextPageLink(); - } - } - } - - protected abstract TextPageData findEntities(I id, TextPageLink pageLink); - - protected abstract void updateEntity(D entity); - - } - } \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java b/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java new file mode 100644 index 0000000000..b57d340d1b --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java @@ -0,0 +1,46 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.install.update; + +import org.thingsboard.server.common.data.SearchTextBased; +import org.thingsboard.server.common.data.id.UUIDBased; +import org.thingsboard.server.common.data.page.TextPageData; +import org.thingsboard.server.common.data.page.TextPageLink; + +public abstract class PaginatedUpdater> { + + private static final int DEFAULT_LIMIT = 100; + + public void updateEntities(I id) { + TextPageLink pageLink = new TextPageLink(DEFAULT_LIMIT); + boolean hasNext = true; + while (hasNext) { + TextPageData entities = findEntities(id, pageLink); + for (D entity : entities.getData()) { + updateEntity(entity); + } + hasNext = entities.hasNext(); + if (hasNext) { + pageLink = entities.getNextPageLink(); + } + } + } + + protected abstract TextPageData findEntities(I id, TextPageLink pageLink); + + protected abstract void updateEntity(D entity); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java b/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java index f0a214c7aa..6b6e2553fa 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java +++ b/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java @@ -51,6 +51,9 @@ import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.security.permission.AccessControlService; +import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.telemetry.exception.ToErrorResponseEntity; import javax.annotation.Nullable; @@ -95,6 +98,9 @@ public class AccessValidator { @Autowired protected EntityViewService entityViewService; + @Autowired + protected AccessControlService accessControlService; + private ExecutorService executor; @PostConstruct @@ -109,30 +115,30 @@ public class AccessValidator { } } - public DeferredResult validateEntityAndCallback(SecurityUser currentUser, String entityType, String entityIdStr, + public DeferredResult validateEntityAndCallback(SecurityUser currentUser, Operation operation, String entityType, String entityIdStr, ThreeConsumer, TenantId, EntityId> onSuccess) throws ThingsboardException { - return validateEntityAndCallback(currentUser, entityType, entityIdStr, onSuccess, (result, t) -> handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR)); + return validateEntityAndCallback(currentUser, operation, entityType, entityIdStr, onSuccess, (result, t) -> handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR)); } - public DeferredResult validateEntityAndCallback(SecurityUser currentUser, String entityType, String entityIdStr, + public DeferredResult validateEntityAndCallback(SecurityUser currentUser, Operation operation, String entityType, String entityIdStr, ThreeConsumer, TenantId, EntityId> onSuccess, BiConsumer, Throwable> onFailure) throws ThingsboardException { - return validateEntityAndCallback(currentUser, EntityIdFactory.getByTypeAndId(entityType, entityIdStr), + return validateEntityAndCallback(currentUser, operation, EntityIdFactory.getByTypeAndId(entityType, entityIdStr), onSuccess, onFailure); } - public DeferredResult validateEntityAndCallback(SecurityUser currentUser, EntityId entityId, + public DeferredResult validateEntityAndCallback(SecurityUser currentUser, Operation operation, EntityId entityId, ThreeConsumer, TenantId, EntityId> onSuccess) throws ThingsboardException { - return validateEntityAndCallback(currentUser, entityId, onSuccess, (result, t) -> handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR)); + return validateEntityAndCallback(currentUser, operation, entityId, onSuccess, (result, t) -> handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR)); } - public DeferredResult validateEntityAndCallback(SecurityUser currentUser, EntityId entityId, + public DeferredResult validateEntityAndCallback(SecurityUser currentUser, Operation operation, EntityId entityId, ThreeConsumer, TenantId, EntityId> onSuccess, BiConsumer, Throwable> onFailure) throws ThingsboardException { final DeferredResult response = new DeferredResult<>(); - validate(currentUser, entityId, new HttpValidationCallback(response, + validate(currentUser, operation, entityId, new HttpValidationCallback(response, new FutureCallback>() { @Override public void onSuccess(@Nullable DeferredResult result) { @@ -148,25 +154,25 @@ public class AccessValidator { return response; } - public void validate(SecurityUser currentUser, EntityId entityId, FutureCallback callback) { + public void validate(SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback callback) { switch (entityId.getEntityType()) { case DEVICE: - validateDevice(currentUser, entityId, callback); + validateDevice(currentUser, operation, entityId, callback); return; case ASSET: - validateAsset(currentUser, entityId, callback); + validateAsset(currentUser, operation, entityId, callback); return; case RULE_CHAIN: - validateRuleChain(currentUser, entityId, callback); + validateRuleChain(currentUser, operation, entityId, callback); return; case CUSTOMER: - validateCustomer(currentUser, entityId, callback); + validateCustomer(currentUser, operation, entityId, callback); return; case TENANT: - validateTenant(currentUser, entityId, callback); + validateTenant(currentUser, operation, entityId, callback); return; case ENTITY_VIEW: - validateEntityView(currentUser, entityId, callback); + validateEntityView(currentUser, operation, entityId, callback); return; default: //TODO: add support of other entities @@ -174,7 +180,7 @@ public class AccessValidator { } } - private void validateDevice(final SecurityUser currentUser, EntityId entityId, FutureCallback callback) { + private void validateDevice(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback callback) { if (currentUser.isSystemAdmin()) { callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); } else { @@ -183,19 +189,18 @@ public class AccessValidator { if (device == null) { return ValidationResult.entityNotFound(DEVICE_WITH_REQUESTED_ID_NOT_FOUND); } else { - if (!device.getTenantId().equals(currentUser.getTenantId())) { - return ValidationResult.accessDenied("Device doesn't belong to the current Tenant!"); - } else if (currentUser.isCustomerUser() && !device.getCustomerId().equals(currentUser.getCustomerId())) { - return ValidationResult.accessDenied("Device doesn't belong to the current Customer!"); - } else { - return ValidationResult.ok(device); + try { + accessControlService.checkPermission(currentUser, Resource.DEVICE, operation, entityId, device); + } catch (ThingsboardException e) { + return ValidationResult.accessDenied(e.getMessage()); } + return ValidationResult.ok(device); } }), executor); } } - private void validateAsset(final SecurityUser currentUser, EntityId entityId, FutureCallback callback) { + private void validateAsset(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback callback) { if (currentUser.isSystemAdmin()) { callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); } else { @@ -204,19 +209,18 @@ public class AccessValidator { if (asset == null) { return ValidationResult.entityNotFound("Asset with requested id wasn't found!"); } else { - if (!asset.getTenantId().equals(currentUser.getTenantId())) { - return ValidationResult.accessDenied("Asset doesn't belong to the current Tenant!"); - } else if (currentUser.isCustomerUser() && !asset.getCustomerId().equals(currentUser.getCustomerId())) { - return ValidationResult.accessDenied("Asset doesn't belong to the current Customer!"); - } else { - return ValidationResult.ok(asset); + try { + accessControlService.checkPermission(currentUser, Resource.ASSET, operation, entityId, asset); + } catch (ThingsboardException e) { + return ValidationResult.accessDenied(e.getMessage()); } + return ValidationResult.ok(asset); } }), executor); } } - private void validateRuleChain(final SecurityUser currentUser, EntityId entityId, FutureCallback callback) { + private void validateRuleChain(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback callback) { if (currentUser.isCustomerUser()) { callback.onSuccess(ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); } else { @@ -225,19 +229,18 @@ public class AccessValidator { if (ruleChain == null) { return ValidationResult.entityNotFound("Rule chain with requested id wasn't found!"); } else { - if (currentUser.isTenantAdmin() && !ruleChain.getTenantId().equals(currentUser.getTenantId())) { - return ValidationResult.accessDenied("Rule chain doesn't belong to the current Tenant!"); - } else if (currentUser.isSystemAdmin() && !ruleChain.getTenantId().isNullUid()) { - return ValidationResult.accessDenied("Rule chain is not in system scope!"); - } else { - return ValidationResult.ok(ruleChain); + try { + accessControlService.checkPermission(currentUser, Resource.RULE_CHAIN, operation, entityId, ruleChain); + } catch (ThingsboardException e) { + return ValidationResult.accessDenied(e.getMessage()); } + return ValidationResult.ok(ruleChain); } }), executor); } } - private void validateRule(final SecurityUser currentUser, EntityId entityId, FutureCallback callback) { + private void validateRule(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback callback) { if (currentUser.isCustomerUser()) { callback.onSuccess(ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); } else { @@ -251,19 +254,18 @@ public class AccessValidator { } else { //TODO: make async RuleChain ruleChain = ruleChainService.findRuleChainById(currentUser.getTenantId(), ruleNode.getRuleChainId()); - if (currentUser.isTenantAdmin() && !ruleChain.getTenantId().equals(currentUser.getTenantId())) { - return ValidationResult.accessDenied("Rule chain doesn't belong to the current Tenant!"); - } else if (currentUser.isSystemAdmin() && !ruleChain.getTenantId().isNullUid()) { - return ValidationResult.accessDenied("Rule chain is not in system scope!"); - } else { - return ValidationResult.ok(ruleNode); + try { + accessControlService.checkPermission(currentUser, Resource.RULE_CHAIN, operation, ruleNode.getRuleChainId(), ruleChain); + } catch (ThingsboardException e) { + return ValidationResult.accessDenied(e.getMessage()); } + return ValidationResult.ok(ruleNode); } }), executor); } } - private void validateCustomer(final SecurityUser currentUser, EntityId entityId, FutureCallback callback) { + private void validateCustomer(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback callback) { if (currentUser.isSystemAdmin()) { callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); } else { @@ -272,19 +274,18 @@ public class AccessValidator { if (customer == null) { return ValidationResult.entityNotFound("Customer with requested id wasn't found!"); } else { - if (!customer.getTenantId().equals(currentUser.getTenantId())) { - return ValidationResult.accessDenied("Customer doesn't belong to the current Tenant!"); - } else if (currentUser.isCustomerUser() && !customer.getId().equals(currentUser.getCustomerId())) { - return ValidationResult.accessDenied("Customer doesn't relate to the currently authorized customer user!"); - } else { - return ValidationResult.ok(customer); + try { + accessControlService.checkPermission(currentUser, Resource.CUSTOMER, operation, entityId, customer); + } catch (ThingsboardException e) { + return ValidationResult.accessDenied(e.getMessage()); } + return ValidationResult.ok(customer); } }), executor); } } - private void validateTenant(final SecurityUser currentUser, EntityId entityId, FutureCallback callback) { + private void validateTenant(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback callback) { if (currentUser.isCustomerUser()) { callback.onSuccess(ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); } else if (currentUser.isSystemAdmin()) { @@ -294,16 +295,19 @@ public class AccessValidator { Futures.addCallback(tenantFuture, getCallback(callback, tenant -> { if (tenant == null) { return ValidationResult.entityNotFound("Tenant with requested id wasn't found!"); - } else if (!tenant.getId().equals(currentUser.getTenantId())) { - return ValidationResult.accessDenied("Tenant doesn't relate to the currently authorized user!"); - } else { - return ValidationResult.ok(tenant); } + try { + accessControlService.checkPermission(currentUser, Resource.TENANT, operation, entityId, tenant); + } catch (ThingsboardException e) { + return ValidationResult.accessDenied(e.getMessage()); + } + return ValidationResult.ok(tenant); + }), executor); } } - private void validateEntityView(final SecurityUser currentUser, EntityId entityId, FutureCallback callback) { + private void validateEntityView(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback callback) { if (currentUser.isSystemAdmin()) { callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); } else { @@ -312,13 +316,12 @@ public class AccessValidator { if (entityView == null) { return ValidationResult.entityNotFound(ENTITY_VIEW_WITH_REQUESTED_ID_NOT_FOUND); } else { - if (!entityView.getTenantId().equals(currentUser.getTenantId())) { - return ValidationResult.accessDenied("Entity-view doesn't belong to the current Tenant!"); - } else if (currentUser.isCustomerUser() && !entityView.getCustomerId().equals(currentUser.getCustomerId())) { - return ValidationResult.accessDenied("Entity-view doesn't belong to the current Customer!"); - } else { - return ValidationResult.ok(entityView); + try { + accessControlService.checkPermission(currentUser, Resource.ENTITY_VIEW, operation, entityId, entityView); + } catch (ThingsboardException e) { + return ValidationResult.accessDenied(e.getMessage()); } + return ValidationResult.ok(entityView); } }), executor); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/AbstractPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/AbstractPermissions.java new file mode 100644 index 0000000000..4a939b586d --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/AbstractPermissions.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.security.permission; + +import java.util.HashMap; +import java.util.Optional; + +public abstract class AbstractPermissions extends HashMap implements Permissions { + + public AbstractPermissions() { + super(); + } + + @Override + public Optional getPermissionChecker(Resource resource) { + PermissionChecker permissionChecker = this.get(resource); + return Optional.ofNullable(permissionChecker); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/AccessControlService.java b/application/src/main/java/org/thingsboard/server/service/security/permission/AccessControlService.java new file mode 100644 index 0000000000..bb18fe8a22 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/AccessControlService.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.security.permission; + +import org.thingsboard.server.common.data.HasCustomerId; +import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.service.security.model.SecurityUser; + +public interface AccessControlService { + + void checkPermission(SecurityUser user, Resource resource, Operation operation) throws ThingsboardException; + + void checkPermission(SecurityUser user, Resource resource, Operation operation, I entityId, T entity) throws ThingsboardException; + +} diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPremissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPremissions.java new file mode 100644 index 0000000000..e12dcd66bd --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPremissions.java @@ -0,0 +1,135 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.security.permission; + +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.*; +import org.thingsboard.server.common.data.id.DashboardId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.service.security.model.SecurityUser; + +import java.util.HashMap; + +@Component(value="customerUserPermissions") +public class CustomerUserPremissions extends AbstractPermissions { + + public CustomerUserPremissions() { + super(); + put(Resource.ALARM, TenantAdminPermissions.tenantEntityPermissionChecker); + put(Resource.ASSET, customerEntityPermissionChecker); + put(Resource.DEVICE, customerEntityPermissionChecker); + put(Resource.CUSTOMER, customerPermissionChecker); + put(Resource.DASHBOARD, customerDashboardPermissionChecker); + put(Resource.ENTITY_VIEW, customerEntityPermissionChecker); + put(Resource.USER, userPermissionChecker); + put(Resource.WIDGETS_BUNDLE, widgetsPermissionChecker); + put(Resource.WIDGET_TYPE, widgetsPermissionChecker); + } + + private static final PermissionChecker customerEntityPermissionChecker = + new PermissionChecker.GenericPermissionChecker(Operation.READ, Operation.READ_CREDENTIALS, Operation.READ_ATTRIBUTES, Operation.READ_TELEMETRY) { + + @Override + public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) { + + if (!super.hasPermission(user, operation, entityId, entity)) { + return false; + } + if (!user.getTenantId().equals(entity.getTenantId())) { + return false; + } + if (!(entity instanceof HasCustomerId)) { + return false; + } + if (!user.getCustomerId().equals(((HasCustomerId)entity).getCustomerId())) { + return false; + } + return true; + } + }; + + private static final PermissionChecker customerPermissionChecker = + new PermissionChecker.GenericPermissionChecker(Operation.READ, Operation.READ_ATTRIBUTES, Operation.READ_TELEMETRY) { + + @Override + public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) { + if (!super.hasPermission(user, operation, entityId, entity)) { + return false; + } + if (!user.getCustomerId().equals(entityId)) { + return false; + } + return true; + } + + }; + + private static final PermissionChecker customerDashboardPermissionChecker = + new PermissionChecker.GenericPermissionChecker(Operation.READ, Operation.READ_ATTRIBUTES, Operation.READ_TELEMETRY) { + + @Override + public boolean hasPermission(SecurityUser user, Operation operation, DashboardId dashboardId, DashboardInfo dashboard) { + + if (!super.hasPermission(user, operation, dashboardId, dashboard)) { + return false; + } + if (!user.getTenantId().equals(dashboard.getTenantId())) { + return false; + } + if (!dashboard.isAssignedToCustomer(user.getCustomerId())) { + return false; + } + return true; + } + + }; + + private static final PermissionChecker userPermissionChecker = new PermissionChecker() { + + @Override + public boolean hasPermission(SecurityUser user, Operation operation, UserId userId, User userEntity) { + if (userEntity.getAuthority() != Authority.CUSTOMER_USER) { + return false; + } + if (!user.getId().equals(userId)) { + return false; + } + return true; + } + + }; + + private static final PermissionChecker widgetsPermissionChecker = new PermissionChecker.GenericPermissionChecker(Operation.READ) { + + @Override + public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) { + if (!super.hasPermission(user, operation, entityId, entity)) { + return false; + } + if (entity.getTenantId() == null || entity.getTenantId().isNullUid()) { + return true; + } + if (!user.getTenantId().equals(entity.getTenantId())) { + return false; + } + return true; + } + + }; +} diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/DefaultAccessControlService.java b/application/src/main/java/org/thingsboard/server/service/security/permission/DefaultAccessControlService.java new file mode 100644 index 0000000000..f80e7fbb80 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/DefaultAccessControlService.java @@ -0,0 +1,91 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.security.permission; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.HasCustomerId; +import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.dao.customer.CustomerService; +import org.thingsboard.server.service.security.model.SecurityUser; + +import java.util.*; + +import static org.thingsboard.server.dao.service.Validator.validateId; + +@Service +@Slf4j +public class DefaultAccessControlService implements AccessControlService { + + private static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; + private static final String YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION = "You don't have permission to perform this operation!"; + + private final Map authorityPermissions = new HashMap<>(); + + public DefaultAccessControlService( + @Qualifier("sysAdminPermissions") Permissions sysAdminPermissions, + @Qualifier("tenantAdminPermissions") Permissions tenantAdminPermissions, + @Qualifier("customerUserPermissions") Permissions customerUserPermissions) { + authorityPermissions.put(Authority.SYS_ADMIN, sysAdminPermissions); + authorityPermissions.put(Authority.TENANT_ADMIN, tenantAdminPermissions); + authorityPermissions.put(Authority.CUSTOMER_USER, customerUserPermissions); + } + + @Override + public void checkPermission(SecurityUser user, Resource resource, Operation operation) throws ThingsboardException { + PermissionChecker permissionChecker = getPermissionChecker(user.getAuthority(), resource); + if (!permissionChecker.hasPermission(user, operation)) { + permissionDenied(); + } + } + + @Override + public void checkPermission(SecurityUser user, Resource resource, + Operation operation, I entityId, T entity) throws ThingsboardException { + PermissionChecker permissionChecker = getPermissionChecker(user.getAuthority(), resource); + if (!permissionChecker.hasPermission(user, operation, entityId, entity)) { + permissionDenied(); + } + } + + private PermissionChecker getPermissionChecker(Authority authority, Resource resource) throws ThingsboardException { + Permissions permissions = authorityPermissions.get(authority); + if (permissions == null) { + permissionDenied(); + } + Optional permissionChecker = permissions.getPermissionChecker(resource); + if (!permissionChecker.isPresent()) { + permissionDenied(); + } + return permissionChecker.get(); + } + + private void permissionDenied() throws ThingsboardException { + throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION, + ThingsboardErrorCode.PERMISSION_DENIED); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/Operation.java b/application/src/main/java/org/thingsboard/server/service/security/permission/Operation.java new file mode 100644 index 0000000000..97f044743c --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/Operation.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.security.permission; + +public enum Operation { + + ALL, CREATE, READ, WRITE, DELETE, ASSIGN_TO_CUSTOMER, UNASSIGN_FROM_CUSTOMER, RPC_CALL, + READ_CREDENTIALS, WRITE_CREDENTIALS, READ_ATTRIBUTES, WRITE_ATTRIBUTES, READ_TELEMETRY, WRITE_TELEMETRY + +} diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/PermissionChecker.java b/application/src/main/java/org/thingsboard/server/service/security/permission/PermissionChecker.java new file mode 100644 index 0000000000..6fe8a0ba07 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/PermissionChecker.java @@ -0,0 +1,73 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.security.permission; + +import org.thingsboard.server.common.data.HasCustomerId; +import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.service.security.model.SecurityUser; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public interface PermissionChecker { + + default boolean hasPermission(SecurityUser user, Operation operation) { + return false; + } + + default boolean hasPermission(SecurityUser user, Operation operation, I entityId, T entity) { + return false; + } + + public class GenericPermissionChecker implements PermissionChecker { + + private final Set allowedOperations; + + public GenericPermissionChecker(Operation... operations) { + allowedOperations = new HashSet(Arrays.asList(operations)); + } + + @Override + public boolean hasPermission(SecurityUser user, Operation operation) { + return allowedOperations.contains(Operation.ALL) || allowedOperations.contains(operation); + } + + @Override + public boolean hasPermission(SecurityUser user, Operation operation, I entityId, T entity) { + return allowedOperations.contains(Operation.ALL) || allowedOperations.contains(operation); + } + } + + public static PermissionChecker denyAllPermissionChecker = new PermissionChecker() {}; + + public static PermissionChecker allowAllPermissionChecker = new PermissionChecker() { + + @Override + public boolean hasPermission(SecurityUser user, Operation operation) { + return true; + } + + @Override + public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) { + return true; + } + }; + + +} diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/Permissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/Permissions.java new file mode 100644 index 0000000000..a99e720787 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/Permissions.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.security.permission; + +import java.util.Optional; + +public interface Permissions { + + Optional getPermissionChecker(Resource resource); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java new file mode 100644 index 0000000000..e086637028 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java @@ -0,0 +1,49 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.security.permission; + +import org.thingsboard.server.common.data.EntityType; + +import java.util.Optional; + +public enum Resource { + ADMIN_SETTINGS(), + ALARM(EntityType.ALARM), + DEVICE(EntityType.DEVICE), + ASSET(EntityType.ASSET), + CUSTOMER(EntityType.CUSTOMER), + DASHBOARD(EntityType.DASHBOARD), + ENTITY_VIEW(EntityType.ENTITY_VIEW), + TENANT(EntityType.TENANT), + RULE_CHAIN(EntityType.RULE_CHAIN), + USER(EntityType.USER), + WIDGETS_BUNDLE(EntityType.WIDGETS_BUNDLE), + WIDGET_TYPE(EntityType.WIDGET_TYPE); + + private final EntityType entityType; + + Resource() { + this.entityType = null; + } + + Resource(EntityType entityType) { + this.entityType = entityType; + } + + public Optional getEntityType() { + return Optional.ofNullable(entityType); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java new file mode 100644 index 0000000000..b2d6df93ee --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java @@ -0,0 +1,68 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.security.permission; + +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.service.security.model.SecurityUser; + +import java.util.HashMap; +import java.util.Optional; + +@Component(value="sysAdminPermissions") +public class SysAdminPermissions extends AbstractPermissions { + + public SysAdminPermissions() { + super(); + put(Resource.ADMIN_SETTINGS, PermissionChecker.allowAllPermissionChecker); + put(Resource.DASHBOARD, new PermissionChecker.GenericPermissionChecker(Operation.READ)); + put(Resource.TENANT, PermissionChecker.allowAllPermissionChecker); + put(Resource.RULE_CHAIN, systemEntityPermissionChecker); + put(Resource.USER, userPermissionChecker); + put(Resource.WIDGETS_BUNDLE, systemEntityPermissionChecker); + put(Resource.WIDGET_TYPE, systemEntityPermissionChecker); + } + + private static final PermissionChecker systemEntityPermissionChecker = new PermissionChecker() { + + @Override + public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) { + + if (entity.getTenantId() != null && !entity.getTenantId().isNullUid()) { + return false; + } + return true; + } + }; + + private static final PermissionChecker userPermissionChecker = new PermissionChecker() { + + @Override + public boolean hasPermission(SecurityUser user, Operation operation, UserId userId, User userEntity) { + if (userEntity.getAuthority() == Authority.CUSTOMER_USER) { + return false; + } + return true; + } + + }; + +} diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java new file mode 100644 index 0000000000..99446db889 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java @@ -0,0 +1,104 @@ +/** + * Copyright © 2016-2019 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.security.permission; + +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.service.security.model.SecurityUser; + +import java.util.HashMap; + +@Component(value="tenantAdminPermissions") +public class TenantAdminPermissions extends AbstractPermissions { + + public TenantAdminPermissions() { + super(); + put(Resource.ALARM, tenantEntityPermissionChecker); + put(Resource.ASSET, tenantEntityPermissionChecker); + put(Resource.DEVICE, tenantEntityPermissionChecker); + put(Resource.CUSTOMER, tenantEntityPermissionChecker); + put(Resource.DASHBOARD, tenantEntityPermissionChecker); + put(Resource.ENTITY_VIEW, tenantEntityPermissionChecker); + put(Resource.TENANT, tenantPermissionChecker); + put(Resource.RULE_CHAIN, tenantEntityPermissionChecker); + put(Resource.USER, userPermissionChecker); + put(Resource.WIDGETS_BUNDLE, widgetsPermissionChecker); + put(Resource.WIDGET_TYPE, widgetsPermissionChecker); + } + + public static final PermissionChecker tenantEntityPermissionChecker = new PermissionChecker() { + + @Override + public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) { + + if (!user.getTenantId().equals(entity.getTenantId())) { + return false; + } + return true; + } + }; + + private static final PermissionChecker tenantPermissionChecker = + new PermissionChecker.GenericPermissionChecker(Operation.READ, Operation.READ_ATTRIBUTES, Operation.READ_TELEMETRY) { + + @Override + public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) { + if (!super.hasPermission(user, operation, entityId, entity)) { + return false; + } + if (!user.getTenantId().equals(entityId)) { + return false; + } + return true; + } + + }; + + private static final PermissionChecker userPermissionChecker = new PermissionChecker() { + + @Override + public boolean hasPermission(SecurityUser user, Operation operation, UserId userId, User userEntity) { + if (userEntity.getAuthority() == Authority.SYS_ADMIN) { + return false; + } + if (!user.getTenantId().equals(userEntity.getTenantId())) { + return false; + } + return true; + } + + }; + + private static final PermissionChecker widgetsPermissionChecker = new PermissionChecker() { + + @Override + public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) { + if (entity.getTenantId() == null || entity.getTenantId().isNullUid()) { + return operation == Operation.READ; + } + if (!user.getTenantId().equals(entity.getTenantId())) { + return false; + } + return true; + } + + }; +} diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java index b012ff9bed..e6b28b33cd 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java @@ -48,6 +48,7 @@ import org.thingsboard.server.service.security.ValidationCallback; import org.thingsboard.server.service.security.ValidationResult; import org.thingsboard.server.service.security.ValidationResultCode; import org.thingsboard.server.service.security.model.UserPrincipal; +import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.telemetry.cmd.AttributesSubscriptionCmd; import org.thingsboard.server.service.telemetry.cmd.GetHistoryCmd; import org.thingsboard.server.service.telemetry.cmd.SubscriptionCmd; @@ -354,9 +355,9 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi }; if (StringUtils.isEmpty(cmd.getScope())) { - accessValidator.validate(sessionRef.getSecurityCtx(), entityId, getAttributesFetchCallback(sessionRef.getSecurityCtx().getTenantId(), entityId, keys, callback)); + accessValidator.validate(sessionRef.getSecurityCtx(), Operation.READ_ATTRIBUTES, entityId, getAttributesFetchCallback(sessionRef.getSecurityCtx().getTenantId(), entityId, keys, callback)); } else { - accessValidator.validate(sessionRef.getSecurityCtx(), entityId, getAttributesFetchCallback(sessionRef.getSecurityCtx().getTenantId(), entityId, cmd.getScope(), keys, callback)); + accessValidator.validate(sessionRef.getSecurityCtx(), Operation.READ_ATTRIBUTES, entityId, getAttributesFetchCallback(sessionRef.getSecurityCtx().getTenantId(), entityId, cmd.getScope(), keys, callback)); } } @@ -406,7 +407,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi sendWsMsg(sessionRef, update); } }; - accessValidator.validate(sessionRef.getSecurityCtx(), entityId, + accessValidator.validate(sessionRef.getSecurityCtx(), Operation.READ_TELEMETRY, entityId, on(r -> Futures.addCallback(tsService.findAll(sessionRef.getSecurityCtx().getTenantId(), entityId, queries), callback, executor), callback::onFailure)); } @@ -436,9 +437,9 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi if (StringUtils.isEmpty(cmd.getScope())) { - accessValidator.validate(sessionRef.getSecurityCtx(), entityId, getAttributesFetchCallback(sessionRef.getSecurityCtx().getTenantId(), entityId, callback)); + accessValidator.validate(sessionRef.getSecurityCtx(), Operation.READ_ATTRIBUTES, entityId, getAttributesFetchCallback(sessionRef.getSecurityCtx().getTenantId(), entityId, callback)); } else { - accessValidator.validate(sessionRef.getSecurityCtx(), entityId, getAttributesFetchCallback(sessionRef.getSecurityCtx().getTenantId(), entityId, cmd.getScope(), callback)); + accessValidator.validate(sessionRef.getSecurityCtx(), Operation.READ_ATTRIBUTES, entityId, getAttributesFetchCallback(sessionRef.getSecurityCtx().getTenantId(), entityId, cmd.getScope(), callback)); } } @@ -474,14 +475,14 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi getLimit(cmd.getLimit()), getAggregation(cmd.getAgg()))).collect(Collectors.toList()); final FutureCallback> callback = getSubscriptionCallback(sessionRef, cmd, sessionId, entityId, startTs, keys); - accessValidator.validate(sessionRef.getSecurityCtx(), entityId, + accessValidator.validate(sessionRef.getSecurityCtx(), Operation.READ_TELEMETRY, entityId, on(r -> Futures.addCallback(tsService.findAll(sessionRef.getSecurityCtx().getTenantId(), entityId, queries), callback, executor), callback::onFailure)); } else { List keys = new ArrayList<>(getKeys(cmd).orElse(Collections.emptySet())); startTs = System.currentTimeMillis(); log.debug("[{}] fetching latest timeseries data for keys: ({}) for device : {}", sessionId, cmd.getKeys(), entityId); final FutureCallback> callback = getSubscriptionCallback(sessionRef, cmd, sessionId, entityId, startTs, keys); - accessValidator.validate(sessionRef.getSecurityCtx(), entityId, + accessValidator.validate(sessionRef.getSecurityCtx(), Operation.READ_TELEMETRY, entityId, on(r -> Futures.addCallback(tsService.findLatest(sessionRef.getSecurityCtx().getTenantId(), entityId, keys), callback, executor), callback::onFailure)); } } @@ -511,7 +512,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi sendWsMsg(sessionRef, update); } }; - accessValidator.validate(sessionRef.getSecurityCtx(), entityId, + accessValidator.validate(sessionRef.getSecurityCtx(), Operation.READ_TELEMETRY, entityId, on(r -> Futures.addCallback(tsService.findAllLatest(sessionRef.getSecurityCtx().getTenantId(), entityId), callback, executor), callback::onFailure)); } diff --git a/common/data/pom.xml b/common/data/pom.xml index cb2d80b8d5..a289ee7d04 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.2.1-SNAPSHOT + 2.3.0 common org.thingsboard.common diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java index e4eb5a53c3..4b8e92d963 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java @@ -22,7 +22,7 @@ import org.thingsboard.server.common.data.id.TenantId; import java.util.*; -public class DashboardInfo extends SearchTextBased implements HasName { +public class DashboardInfo extends SearchTextBased implements HasName, HasTenantId { private TenantId tenantId; private String title; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java index 959bc3643f..fedca0caff 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java @@ -19,5 +19,5 @@ package org.thingsboard.server.common.data; * @author Andrew Shvayka */ public enum EntityType { - TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW + TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java b/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java index ca8417dff9..90df5ebc5a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.common.data; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.id.TenantId; @@ -22,7 +23,7 @@ import org.thingsboard.server.common.data.id.TenantId; import com.fasterxml.jackson.databind.JsonNode; @EqualsAndHashCode(callSuper = true) -public class Tenant extends ContactBased implements HasName { +public class Tenant extends ContactBased implements HasName, HasTenantId { private static final long serialVersionUID = 8057243243859922101L; @@ -51,6 +52,12 @@ public class Tenant extends ContactBased implements HasName { this.title = title; } + @Override + @JsonIgnore + public TenantId getTenantId() { + return getId(); + } + @Override @JsonProperty(access = JsonProperty.Access.READ_ONLY) public String getName() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java index 7c11ec5b6c..d2dae513e4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java @@ -59,6 +59,10 @@ public class EntityIdFactory { return new RuleNodeId(uuid); case ENTITY_VIEW: return new EntityViewId(uuid); + case WIDGETS_BUNDLE: + return new WidgetsBundleId(uuid); + case WIDGET_TYPE: + return new WidgetTypeId(uuid); } throw new IllegalArgumentException("EntityType " + type + " is not supported!"); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetTypeId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetTypeId.java index d15fa94a50..f7d31080f1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetTypeId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetTypeId.java @@ -18,9 +18,11 @@ package org.thingsboard.server.common.data.id; import java.util.UUID; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import org.thingsboard.server.common.data.EntityType; -public final class WidgetTypeId extends UUIDBased { +public final class WidgetTypeId extends UUIDBased implements EntityId { private static final long serialVersionUID = 1L; @@ -29,4 +31,9 @@ public final class WidgetTypeId extends UUIDBased { super(id); } + @JsonIgnore + @Override + public EntityType getEntityType() { + return EntityType.WIDGET_TYPE; + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetsBundleId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetsBundleId.java index c4f5ffe104..a8f1245357 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetsBundleId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetsBundleId.java @@ -18,9 +18,11 @@ package org.thingsboard.server.common.data.id; import java.util.UUID; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import org.thingsboard.server.common.data.EntityType; -public final class WidgetsBundleId extends UUIDBased { +public final class WidgetsBundleId extends UUIDBased implements EntityId { private static final long serialVersionUID = 1L; @@ -29,4 +31,9 @@ public final class WidgetsBundleId extends UUIDBased { super(id); } + @JsonIgnore + @Override + public EntityType getEntityType() { + return EntityType.WIDGETS_BUNDLE; + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetType.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetType.java index 4f23a95c56..757f50bc94 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetType.java @@ -18,11 +18,12 @@ package org.thingsboard.server.common.data.widget; import com.fasterxml.jackson.databind.JsonNode; import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.WidgetTypeId; @EqualsAndHashCode(callSuper = true) -public class WidgetType extends BaseData { +public class WidgetType extends BaseData implements HasTenantId { private static final long serialVersionUID = 8388684344603660756L; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java index 3af041357e..1924634d30 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java @@ -15,13 +15,14 @@ */ package org.thingsboard.server.common.data.widget; +import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.SearchTextBased; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.WidgetsBundleId; import java.util.Arrays; -public class WidgetsBundle extends SearchTextBased { +public class WidgetsBundle extends SearchTextBased implements HasTenantId { private static final long serialVersionUID = -7627368878362410489L; diff --git a/common/message/pom.xml b/common/message/pom.xml index 9814332be8..24e2d8bb38 100644 --- a/common/message/pom.xml +++ b/common/message/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.2.1-SNAPSHOT + 2.3.0 common org.thingsboard.common diff --git a/common/pom.xml b/common/pom.xml index 1b8db184a7..bb689002e1 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.2.1-SNAPSHOT + 2.3.0 thingsboard common diff --git a/common/queue/pom.xml b/common/queue/pom.xml index 99ceb77ef5..aa5542cede 100644 --- a/common/queue/pom.xml +++ b/common/queue/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.2.1-SNAPSHOT + 2.3.0 common org.thingsboard.common diff --git a/common/transport/coap/pom.xml b/common/transport/coap/pom.xml index 2aacfcba49..b27fcd7763 100644 --- a/common/transport/coap/pom.xml +++ b/common/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 2.2.1-SNAPSHOT + 2.3.0 transport org.thingsboard.common.transport diff --git a/common/transport/http/pom.xml b/common/transport/http/pom.xml index a94f5c29f3..e867aa1605 100644 --- a/common/transport/http/pom.xml +++ b/common/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 2.2.1-SNAPSHOT + 2.3.0 transport org.thingsboard.common.transport diff --git a/common/transport/mqtt/pom.xml b/common/transport/mqtt/pom.xml index ce931f6e24..95832aac51 100644 --- a/common/transport/mqtt/pom.xml +++ b/common/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 2.2.1-SNAPSHOT + 2.3.0 transport org.thingsboard.common.transport diff --git a/common/transport/pom.xml b/common/transport/pom.xml index 50033be4aa..b63d6488bd 100644 --- a/common/transport/pom.xml +++ b/common/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.2.1-SNAPSHOT + 2.3.0 common org.thingsboard.common diff --git a/common/transport/transport-api/pom.xml b/common/transport/transport-api/pom.xml index 417f3d565f..407c414fce 100644 --- a/common/transport/transport-api/pom.xml +++ b/common/transport/transport-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 2.2.1-SNAPSHOT + 2.3.0 transport org.thingsboard.common.transport diff --git a/dao/pom.xml b/dao/pom.xml index 1b609f422f..817a5449f6 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.2.1-SNAPSHOT + 2.3.0 thingsboard dao diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/TsKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/TsKvEntity.java index d5a0968dd4..873f8e8c42 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/TsKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/TsKvEntity.java @@ -62,49 +62,53 @@ public final class TsKvEntity implements ToData { } public TsKvEntity(Long longValue, Double doubleValue, Long longCountValue, Long doubleCountValue, String aggType) { - switch (aggType) { - case AVG: - double sum = 0.0; - if (longValue != null) { - sum += longValue; - } - if (doubleValue != null) { - sum += doubleValue; - } - long totalCount = longCountValue + doubleCountValue; - if (totalCount > 0) { - this.doubleValue = sum / (longCountValue + doubleCountValue); - } else { - this.doubleValue = 0.0; - } - break; - case SUM: - if (doubleCountValue > 0) { - this.doubleValue = doubleValue + (longValue != null ? longValue.doubleValue() : 0.0); - } else { - this.longValue = longValue; - } - break; - case MIN: - case MAX: - if (longCountValue > 0 && doubleCountValue > 0) { - this.doubleValue = MAX.equals(aggType) ? Math.max(doubleValue, longValue.doubleValue()) : Math.min(doubleValue, longValue.doubleValue()); - } else if (doubleCountValue > 0) { - this.doubleValue = doubleValue; - } else if (longCountValue > 0) { - this.longValue = longValue; - } - break; + if(!isAllNull(longValue, doubleValue, longCountValue, doubleCountValue)) { + switch (aggType) { + case AVG: + double sum = 0.0; + if (longValue != null) { + sum += longValue; + } + if (doubleValue != null) { + sum += doubleValue; + } + long totalCount = longCountValue + doubleCountValue; + if (totalCount > 0) { + this.doubleValue = sum / (longCountValue + doubleCountValue); + } else { + this.doubleValue = 0.0; + } + break; + case SUM: + if (doubleCountValue > 0) { + this.doubleValue = doubleValue + (longValue != null ? longValue.doubleValue() : 0.0); + } else { + this.longValue = longValue; + } + break; + case MIN: + case MAX: + if (longCountValue > 0 && doubleCountValue > 0) { + this.doubleValue = MAX.equals(aggType) ? Math.max(doubleValue, longValue.doubleValue()) : Math.min(doubleValue, longValue.doubleValue()); + } else if (doubleCountValue > 0) { + this.doubleValue = doubleValue; + } else if (longCountValue > 0) { + this.longValue = longValue; + } + break; + } } } public TsKvEntity(Long booleanValueCount, Long strValueCount, Long longValueCount, Long doubleValueCount) { - if (booleanValueCount != 0) { - this.longValue = booleanValueCount; - } else if (strValueCount != 0) { - this.longValue = strValueCount; - } else { - this.longValue = longValueCount + doubleValueCount; + if(!isAllNull(booleanValueCount, strValueCount, longValueCount, doubleValueCount)) { + if (booleanValueCount != 0) { + this.longValue = booleanValueCount; + } else if (strValueCount != 0) { + this.longValue = strValueCount; + } else { + this.longValue = longValueCount + doubleValueCount; + } } } @@ -155,4 +159,13 @@ public final class TsKvEntity implements ToData { public boolean isNotEmpty() { return strValue != null || longValue != null || doubleValue != null || booleanValue != null; } + + private static boolean isAllNull(Object... args) { + for (Object arg : args) { + if(arg != null) { + return false; + } + } + return true; + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java index c0dc480912..3852ec175e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java @@ -287,6 +287,20 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC return ruleNodes; } + @Override + public List getReferencingRuleChainNodes(TenantId tenantId, RuleChainId ruleChainId) { + Validator.validateId(ruleChainId, "Incorrect rule chain id for search request."); + List relations = getNodeToRuleChainRelations(tenantId, ruleChainId); + List ruleNodes = new ArrayList<>(); + for (EntityRelation relation : relations) { + RuleNode ruleNode = ruleNodeDao.findById(tenantId, relation.getFrom().getId()); + if (ruleNode != null) { + ruleNodes.add(ruleNode); + } + } + return ruleNodes; + } + @Override public List getRuleNodeRelations(TenantId tenantId, RuleNodeId ruleNodeId) { Validator.validateId(ruleNodeId, "Incorrect rule node id for search request."); @@ -351,6 +365,10 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC return relationService.findByFrom(tenantId, ruleChainId, RelationTypeGroup.RULE_CHAIN); } + private List getNodeToRuleChainRelations(TenantId tenantId, RuleChainId ruleChainId) { + return relationService.findByTo(tenantId, ruleChainId, RelationTypeGroup.RULE_NODE); + } + private void deleteRuleNode(TenantId tenantId, EntityId entityId) { deleteEntityRelations(tenantId, entityId); ruleNodeDao.removeById(tenantId, entityId.getId()); diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java index 450190a39b..5a1949e699 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java @@ -53,6 +53,8 @@ public interface RuleChainService { List getRuleChainNodes(TenantId tenantId, RuleChainId ruleChainId); + List getReferencingRuleChainNodes(TenantId tenantId, RuleChainId ruleChainId); + List getRuleNodeRelations(TenantId tenantId, RuleNodeId ruleNodeId); TextPageData findTenantRuleChains(TenantId tenantId, TextPageLink pageLink); diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java index c34d4bdda7..3b56dedea2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java @@ -148,7 +148,7 @@ public class BaseTimeseriesService implements TimeseriesService { private void saveAndRegisterFutures(TenantId tenantId, List> futures, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) { - throw new IncorrectParameterException("Telemetry data can't be stored for entity view. Only read only"); + throw new IncorrectParameterException("Telemetry data can't be stored for entity view. Read only"); } futures.add(timeseriesDao.savePartition(tenantId, entityId, tsKvEntry.getTs(), tsKvEntry.getKey(), ttl)); futures.add(timeseriesDao.saveLatest(tenantId, entityId, tsKvEntry)); diff --git a/dao/src/test/resources/application-test.properties b/dao/src/test/resources/application-test.properties index a285676f08..7440a966fe 100644 --- a/dao/src/test/resources/application-test.properties +++ b/dao/src/test/resources/application-test.properties @@ -27,9 +27,6 @@ caffeine.specs.assets.maxSize=100000 caffeine.specs.entityViews.timeToLiveInMinutes=1440 caffeine.specs.entityViews.maxSize=100000 -caching.specs.devices.timeToLiveInMinutes=1440 -caching.specs.devices.maxSize=100000 - redis.connection.host=localhost redis.connection.port=6379 redis.connection.db=0 diff --git a/docker/docker-upgrade-tb.sh b/docker/docker-upgrade-tb.sh index 70039e4d0f..c13d62707f 100755 --- a/docker/docker-upgrade-tb.sh +++ b/docker/docker-upgrade-tb.sh @@ -46,8 +46,6 @@ ADDITIONAL_STARTUP_SERVICES=$(additionalStartupServices) || exit $? docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS pull tb1 -if [ ! -z "${ADDITIONAL_STARTUP_SERVICES// }" ]; then - docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS up -d redis $ADDITIONAL_STARTUP_SERVICES -fi +docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS up -d redis $ADDITIONAL_STARTUP_SERVICES docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS run --no-deps --rm -e UPGRADE_TB=true -e FROM_VERSION=${fromVersion} tb1 diff --git a/msa/black-box-tests/pom.xml b/msa/black-box-tests/pom.xml index 1ddbe303d7..813d344605 100644 --- a/msa/black-box-tests/pom.xml +++ b/msa/black-box-tests/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 2.2.1-SNAPSHOT + 2.3.0 msa org.thingsboard.msa diff --git a/msa/js-executor/package-lock.json b/msa/js-executor/package-lock.json index f232cca82b..1af16309a7 100644 --- a/msa/js-executor/package-lock.json +++ b/msa/js-executor/package-lock.json @@ -1,6 +1,6 @@ { "name": "thingsboard-js-executor", - "version": "2.2.1", + "version": "2.3.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/msa/js-executor/package.json b/msa/js-executor/package.json index e47e6bc1bf..7ec6f92e7e 100644 --- a/msa/js-executor/package.json +++ b/msa/js-executor/package.json @@ -1,7 +1,7 @@ { "name": "thingsboard-js-executor", "private": true, - "version": "2.2.1", + "version": "2.3.0", "description": "ThingsBoard JavaScript Executor Microservice", "main": "server.js", "bin": "server.js", diff --git a/msa/js-executor/pom.xml b/msa/js-executor/pom.xml index 607d0e1762..97c4b166b5 100644 --- a/msa/js-executor/pom.xml +++ b/msa/js-executor/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.2.1-SNAPSHOT + 2.3.0 msa org.thingsboard.msa diff --git a/msa/pom.xml b/msa/pom.xml index f06773330c..180ecae29a 100644 --- a/msa/pom.xml +++ b/msa/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.2.1-SNAPSHOT + 2.3.0 thingsboard msa diff --git a/msa/tb-node/pom.xml b/msa/tb-node/pom.xml index fee65779f3..bc0bd0ee60 100644 --- a/msa/tb-node/pom.xml +++ b/msa/tb-node/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.2.1-SNAPSHOT + 2.3.0 msa org.thingsboard.msa diff --git a/msa/tb/pom.xml b/msa/tb/pom.xml index 290c5b2aee..f1eb88fffc 100644 --- a/msa/tb/pom.xml +++ b/msa/tb/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.2.1-SNAPSHOT + 2.3.0 msa org.thingsboard.msa @@ -40,7 +40,7 @@ tb-cassandra thingsboard /usr/share/${pkg.name} - 2.1.3 + 2.2.0 diff --git a/msa/transport/coap/pom.xml b/msa/transport/coap/pom.xml index 303b36b6fb..df185220a2 100644 --- a/msa/transport/coap/pom.xml +++ b/msa/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 2.2.1-SNAPSHOT + 2.3.0 transport org.thingsboard.msa.transport diff --git a/msa/transport/http/pom.xml b/msa/transport/http/pom.xml index 9e9255d828..628f0665eb 100644 --- a/msa/transport/http/pom.xml +++ b/msa/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 2.2.1-SNAPSHOT + 2.3.0 transport org.thingsboard.msa.transport diff --git a/msa/transport/mqtt/pom.xml b/msa/transport/mqtt/pom.xml index 2aeebb6481..33eeaf9146 100644 --- a/msa/transport/mqtt/pom.xml +++ b/msa/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 2.2.1-SNAPSHOT + 2.3.0 transport org.thingsboard.msa.transport diff --git a/msa/transport/pom.xml b/msa/transport/pom.xml index 0d24b58b58..644605eafe 100644 --- a/msa/transport/pom.xml +++ b/msa/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.2.1-SNAPSHOT + 2.3.0 msa org.thingsboard.msa diff --git a/msa/web-ui/package-lock.json b/msa/web-ui/package-lock.json index 11cf32b3bd..c50ee7d073 100644 --- a/msa/web-ui/package-lock.json +++ b/msa/web-ui/package-lock.json @@ -1,6 +1,6 @@ { "name": "thingsboard-web-ui", - "version": "2.2.1", + "version": "2.3.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/msa/web-ui/package.json b/msa/web-ui/package.json index cd08852bc7..43a7962aee 100644 --- a/msa/web-ui/package.json +++ b/msa/web-ui/package.json @@ -1,7 +1,7 @@ { "name": "thingsboard-web-ui", "private": true, - "version": "2.2.1", + "version": "2.3.0", "description": "ThingsBoard Web UI Microservice", "main": "server.js", "bin": "server.js", diff --git a/msa/web-ui/pom.xml b/msa/web-ui/pom.xml index a5300bd02b..3768993e9b 100644 --- a/msa/web-ui/pom.xml +++ b/msa/web-ui/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.2.1-SNAPSHOT + 2.3.0 msa org.thingsboard.msa diff --git a/netty-mqtt/pom.xml b/netty-mqtt/pom.xml index 6b427f015b..4e69394015 100644 --- a/netty-mqtt/pom.xml +++ b/netty-mqtt/pom.xml @@ -19,12 +19,12 @@ 4.0.0 org.thingsboard - 2.2.1-SNAPSHOT + 2.3.0 thingsboard org.thingsboard netty-mqtt - 2.2.1-SNAPSHOT + 2.3.0 jar Netty MQTT Client diff --git a/pom.xml b/pom.xml index 61e0a1e55a..794aa2ff9b 100755 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard thingsboard - 2.2.1-SNAPSHOT + 2.3.0 pom Thingsboard diff --git a/rule-engine/pom.xml b/rule-engine/pom.xml index 6111b0cbf7..a704dd7d4e 100644 --- a/rule-engine/pom.xml +++ b/rule-engine/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.2.1-SNAPSHOT + 2.3.0 thingsboard rule-engine diff --git a/rule-engine/rule-engine-api/pom.xml b/rule-engine/rule-engine-api/pom.xml index 7dc0956d9a..15f629a5d8 100644 --- a/rule-engine/rule-engine-api/pom.xml +++ b/rule-engine/rule-engine-api/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 2.2.1-SNAPSHOT + 2.3.0 rule-engine org.thingsboard.rule-engine diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml index d01ddbf0a8..d95003c9df 100644 --- a/rule-engine/rule-engine-components/pom.xml +++ b/rule-engine/rule-engine-components/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 2.2.1-SNAPSHOT + 2.3.0 rule-engine org.thingsboard.rule-engine diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java index 4e0d964068..f7a4f8023e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java @@ -22,6 +22,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; @@ -78,28 +79,28 @@ public abstract class TbAbstractRelationActionNode ctx.tellNext(msg, filterResult ? SUCCESS : FAILURE), t -> ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor()); + filterResult -> ctx.tellNext(filterResult.getMsg(), filterResult.isResult() ? SUCCESS : FAILURE), t -> ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor()); } @Override public void destroy() { } - protected ListenableFuture processEntityRelationAction(TbContext ctx, TbMsg msg) { + protected ListenableFuture processEntityRelationAction(TbContext ctx, TbMsg msg) { return Futures.transformAsync(getEntity(ctx, msg), entityContainer -> doProcessEntityRelationAction(ctx, msg, entityContainer)); } protected abstract boolean createEntityIfNotExists(); - protected abstract ListenableFuture doProcessEntityRelationAction(TbContext ctx, TbMsg msg, EntityContainer entityContainer); + protected abstract ListenableFuture doProcessEntityRelationAction(TbContext ctx, TbMsg msg, EntityContainer entityContainer); protected abstract C loadEntityNodeActionConfig(TbNodeConfiguration configuration) throws TbNodeException; protected ListenableFuture getEntity(TbContext ctx, TbMsg msg) { - String entityName = TbNodeUtils.processPattern(this.config.getEntityNamePattern(), msg.getMetaData()); + String entityName = processPattern(msg, this.config.getEntityNamePattern()); String type; if (this.config.getEntityTypePattern() != null) { - type = TbNodeUtils.processPattern(this.config.getEntityTypePattern(), msg.getMetaData()); + type = processPattern(msg, this.config.getEntityTypePattern()); } else { type = null; } @@ -116,24 +117,30 @@ public abstract class TbAbstractRelationActionNode> processListSearchDirection(TbContext ctx, TbMsg msg) { - if (EntitySearchDirection.FROM.name().equals(config.getDirection())) { - return ctx.getRelationService().findByToAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON); + if (EntitySearchDirection.FROM.name().equals(this.config.getDirection())) { + return ctx.getRelationService().findByToAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), processPattern(msg, this.config.getRelationTypePattern()), RelationTypeGroup.COMMON); } else { - return ctx.getRelationService().findByFromAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON); + return ctx.getRelationService().findByFromAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), processPattern(msg, this.config.getRelationTypePattern()), RelationTypeGroup.COMMON); } } + protected String processPattern(TbMsg msg, String pattern){ + return TbNodeUtils.processPattern(pattern, msg.getMetaData()); + } + @Data @AllArgsConstructor private static class EntityKey { @@ -146,6 +153,7 @@ public abstract class TbAbstractRelationActionNode { @@ -233,7 +241,15 @@ public abstract class TbAbstractRelationActionNode" + + " In case that relation from the message originator to the selected entity not exist and If selected checkbox 'Remove current relations'," + + " before creating the new relation all existed relations to message originator by type and direction will be removed.
" + + " If relation from the message originator to the selected entity created and If selected checkbox 'Change originator to related entity'," + + " outbound message will be processed as a message from this entity.", nodeDetails = "If the relation already exists or successfully created - Message send via Success chain, otherwise Failure chain will be used.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeCreateRelationConfig", @@ -49,6 +56,8 @@ import org.thingsboard.server.common.msg.TbMsg; ) public class TbCreateRelationNode extends TbAbstractRelationActionNode { + private String relationType; + @Override protected TbCreateRelationNodeConfiguration loadEntityNodeActionConfig(TbNodeConfiguration configuration) throws TbNodeException { return TbNodeUtils.convert(configuration, TbCreateRelationNodeConfiguration.class); @@ -60,19 +69,60 @@ public class TbCreateRelationNode extends TbAbstractRelationActionNode doProcessEntityRelationAction(TbContext ctx, TbMsg msg, EntityContainer entity) { - return createIfAbsent(ctx, msg, entity); + protected ListenableFuture doProcessEntityRelationAction(TbContext ctx, TbMsg msg, EntityContainer entity) { + ListenableFuture future = createIfAbsent(ctx, msg, entity); + return Futures.transform(future, result -> { + RelationContainer container = new RelationContainer(); + if (result && config.isChangeOriginatorToRelatedEntity()) { + TbMsg tbMsg = ctx.transformMsg(msg, msg.getType(), entity.getEntityId(), msg.getMetaData(), msg.getData()); + container.setMsg(tbMsg); + } else { + container.setMsg(msg); + } + container.setResult(result); + return container; + }); } private ListenableFuture createIfAbsent(TbContext ctx, TbMsg msg, EntityContainer entityContainer) { + relationType = processPattern(msg, config.getRelationTypePattern()); SearchDirectionIds sdId = processSingleSearchDirection(msg, entityContainer); - return Futures.transformAsync(ctx.getRelationService().checkRelation(ctx.getTenantId(), sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON), - result -> { - if (!result) { - return processCreateRelation(ctx, entityContainer, sdId); - } - return Futures.immediateFuture(true); - }); + ListenableFuture checkRelationFuture = Futures.transformAsync(ctx.getRelationService().checkRelation(ctx.getTenantId(), sdId.getFromId(), sdId.getToId(), relationType, RelationTypeGroup.COMMON), result -> { + if (!result) { + if (config.isRemoveCurrentRelations()) { + return processDeleteRelations(ctx, processFindRelations(ctx, msg, sdId)); + } + } + return Futures.immediateFuture(true); + }, ctx.getDbCallbackExecutor()); + + return Futures.transformAsync(checkRelationFuture, result -> { + if (!result) { + return processCreateRelation(ctx, entityContainer, sdId); + } + return Futures.immediateFuture(true); + }, ctx.getDbCallbackExecutor()); + } + + private ListenableFuture> processFindRelations(TbContext ctx, TbMsg msg, SearchDirectionIds sdId) { + if (sdId.isOrignatorDirectionFrom()) { + return ctx.getRelationService().findByFromAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), relationType, RelationTypeGroup.COMMON); + } else { + return ctx.getRelationService().findByToAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), relationType, RelationTypeGroup.COMMON); + } + } + + private ListenableFuture processDeleteRelations(TbContext ctx, ListenableFuture> listListenableFuture) { + return Futures.transformAsync(listListenableFuture, entityRelations -> { + if (!entityRelations.isEmpty()) { + List> list = new ArrayList<>(); + for (EntityRelation relation : entityRelations) { + list.add(ctx.getRelationService().deleteRelationAsync(ctx.getTenantId(), relation)); + } + return Futures.transform(Futures.allAsList(list), result -> false); + } + return Futures.immediateFuture(false); + }, ctx.getDbCallbackExecutor()); } private ListenableFuture processCreateRelation(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) { @@ -96,17 +146,17 @@ public class TbCreateRelationNode extends TbAbstractRelationActionNode processView(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) { return Futures.transformAsync(ctx.getEntityViewService().findEntityViewByIdAsync(ctx.getTenantId(), new EntityViewId(entityContainer.getEntityId().getId())), entityView -> { if (entityView != null) { - return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON)); + return processSave(ctx, sdId); } else { return Futures.immediateFuture(true); } - }); + }, ctx.getDbCallbackExecutor()); } private ListenableFuture processDevice(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) { return Futures.transformAsync(ctx.getDeviceService().findDeviceByIdAsync(ctx.getTenantId(), new DeviceId(entityContainer.getEntityId().getId())), device -> { if (device != null) { - return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON)); + return processSave(ctx, sdId); } else { return Futures.immediateFuture(true); } @@ -116,40 +166,45 @@ public class TbCreateRelationNode extends TbAbstractRelationActionNode processAsset(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) { return Futures.transformAsync(ctx.getAssetService().findAssetByIdAsync(ctx.getTenantId(), new AssetId(entityContainer.getEntityId().getId())), asset -> { if (asset != null) { - return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON)); + return processSave(ctx, sdId); } else { return Futures.immediateFuture(true); } - }); + }, ctx.getDbCallbackExecutor()); } private ListenableFuture processCustomer(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) { return Futures.transformAsync(ctx.getCustomerService().findCustomerByIdAsync(ctx.getTenantId(), new CustomerId(entityContainer.getEntityId().getId())), customer -> { if (customer != null) { - return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON)); + return processSave(ctx, sdId); } else { return Futures.immediateFuture(true); } - }); + }, ctx.getDbCallbackExecutor()); } private ListenableFuture processDashboard(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) { return Futures.transformAsync(ctx.getDashboardService().findDashboardByIdAsync(ctx.getTenantId(), new DashboardId(entityContainer.getEntityId().getId())), dashboard -> { if (dashboard != null) { - return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON)); + return processSave(ctx, sdId); } else { return Futures.immediateFuture(true); } - }); + }, ctx.getDbCallbackExecutor()); } private ListenableFuture processTenant(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) { return Futures.transformAsync(ctx.getTenantService().findTenantByIdAsync(ctx.getTenantId(), new TenantId(entityContainer.getEntityId().getId())), tenant -> { if (tenant != null) { - return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON)); + return processSave(ctx, sdId); } else { return Futures.immediateFuture(true); } - }); + }, ctx.getDbCallbackExecutor()); } + + private ListenableFuture processSave(TbContext ctx, SearchDirectionIds sdId) { + return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(sdId.getFromId(), sdId.getToId(), relationType, RelationTypeGroup.COMMON)); + } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeConfiguration.java index 20a21bf783..2600c423a7 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeConfiguration.java @@ -23,15 +23,19 @@ import org.thingsboard.server.common.data.relation.EntitySearchDirection; public class TbCreateRelationNodeConfiguration extends TbAbstractRelationActionNodeConfiguration implements NodeConfiguration { private boolean createEntityIfNotExists; + private boolean changeOriginatorToRelatedEntity; + private boolean removeCurrentRelations; @Override public TbCreateRelationNodeConfiguration defaultConfiguration() { TbCreateRelationNodeConfiguration configuration = new TbCreateRelationNodeConfiguration(); configuration.setDirection(EntitySearchDirection.FROM.name()); - configuration.setRelationType("Contains"); + configuration.setRelationTypePattern("Contains"); configuration.setEntityNamePattern(""); configuration.setEntityCacheExpiration(300); configuration.setCreateEntityIfNotExists(false); + configuration.setRemoveCurrentRelations(false); + configuration.setChangeOriginatorToRelatedEntity(false); return configuration; } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNode.java index 1c277f299c..ff4172c128 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNode.java @@ -42,11 +42,13 @@ import java.util.List; " if 'Delete single entity' is set to true, otherwise rule node will delete all relations to the originator of the message by type and direction.", nodeDetails = "If the relation(s) successfully deleted - Message send via Success chain, otherwise Failure chain will be used.", uiResources = {"static/rulenode/rulenode-core-config.js"}, - configDirective ="tbActionNodeDeleteRelationConfig", + configDirective = "tbActionNodeDeleteRelationConfig", icon = "remove_circle" ) public class TbDeleteRelationNode extends TbAbstractRelationActionNode { + private String relationType; + @Override protected TbDeleteRelationNodeConfiguration loadEntityNodeActionConfig(TbNodeConfiguration configuration) throws TbNodeException { return TbNodeUtils.convert(configuration, TbDeleteRelationNodeConfiguration.class); @@ -58,35 +60,39 @@ public class TbDeleteRelationNode extends TbAbstractRelationActionNode processEntityRelationAction(TbContext ctx, TbMsg msg) { - if(config.isDeleteForSingleEntity()){ - return Futures.transformAsync(getEntity(ctx, msg), entityContainer -> doProcessEntityRelationAction(ctx, msg, entityContainer)); - } else { - return processList(ctx, msg); - } + protected ListenableFuture processEntityRelationAction(TbContext ctx, TbMsg msg) { + return getRelationContainerListenableFuture(ctx, msg); } @Override - protected ListenableFuture doProcessEntityRelationAction(TbContext ctx, TbMsg msg, EntityContainer entityContainer) { - return processSingle(ctx, msg, entityContainer); + protected ListenableFuture doProcessEntityRelationAction(TbContext ctx, TbMsg msg, EntityContainer entityContainer) { + return Futures.transform(processSingle(ctx, msg, entityContainer), result -> new RelationContainer(msg, result)); } + private ListenableFuture getRelationContainerListenableFuture(TbContext ctx, TbMsg msg) { + relationType = processPattern(msg, config.getRelationTypePattern()); + if (config.isDeleteForSingleEntity()) { + return Futures.transformAsync(getEntity(ctx, msg), entityContainer -> doProcessEntityRelationAction(ctx, msg, entityContainer)); + } else { + return Futures.transform(processList(ctx, msg), result -> new RelationContainer(msg, result)); + } + } private ListenableFuture processList(TbContext ctx, TbMsg msg) { return Futures.transformAsync(processListSearchDirection(ctx, msg), entityRelations -> { - if(entityRelations.isEmpty()){ + if (entityRelations.isEmpty()) { return Futures.immediateFuture(true); } else { List> listenableFutureList = new ArrayList<>(); - for (EntityRelation entityRelation: entityRelations) { + for (EntityRelation entityRelation : entityRelations) { listenableFutureList.add(ctx.getRelationService().deleteRelationAsync(ctx.getTenantId(), entityRelation)); } return Futures.transformAsync(Futures.allAsList(listenableFutureList), booleans -> { - for (Boolean bool : booleans) { - if (!bool) { - return Futures.immediateFuture(false); - } - } - return Futures.immediateFuture(true); + for (Boolean bool : booleans) { + if (!bool) { + return Futures.immediateFuture(false); + } + } + return Futures.immediateFuture(true); }); } }); @@ -94,8 +100,7 @@ public class TbDeleteRelationNode extends TbAbstractRelationActionNode processSingle(TbContext ctx, TbMsg msg, EntityContainer entityContainer) { SearchDirectionIds sdId = processSingleSearchDirection(msg, entityContainer); - return Futures.transformAsync(ctx.getRelationService().checkRelation(ctx.getTenantId(), sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON), - + return Futures.transformAsync(ctx.getRelationService().checkRelation(ctx.getTenantId(), sdId.getFromId(), sdId.getToId(), relationType, RelationTypeGroup.COMMON), result -> { if (result) { return processSingleDeleteRelation(ctx, sdId); @@ -105,7 +110,7 @@ public class TbDeleteRelationNode extends TbAbstractRelationActionNode processSingleDeleteRelation(TbContext ctx, SearchDirectionIds sdId) { - return ctx.getRelationService().deleteRelationAsync(ctx.getTenantId(), sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON); + return ctx.getRelationService().deleteRelationAsync(ctx.getTenantId(), sdId.getFromId(), sdId.getToId(), relationType, RelationTypeGroup.COMMON); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNodeConfiguration.java index 3304f1a855..d9a6a2df1a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNodeConfiguration.java @@ -29,7 +29,7 @@ public class TbDeleteRelationNodeConfiguration extends TbAbstractRelationActionN TbDeleteRelationNodeConfiguration configuration = new TbDeleteRelationNodeConfiguration(); configuration.setDeleteForSingleEntity(true); configuration.setDirection(EntitySearchDirection.FROM.name()); - configuration.setRelationType("Contains"); + configuration.setRelationTypePattern("Contains"); configuration.setEntityNamePattern(""); configuration.setEntityCacheExpiration(300); return configuration; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java index 7e548268c3..e290c18d14 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java @@ -71,10 +71,14 @@ public class TbRestApiCallNode implements TbNode { public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { try { this.config = TbNodeUtils.convert(configuration, TbRestApiCallNodeConfiguration.class); - this.eventLoopGroup = new NioEventLoopGroup(); - Netty4ClientHttpRequestFactory nettyFactory = new Netty4ClientHttpRequestFactory(this.eventLoopGroup); - nettyFactory.setSslContext(SslContextBuilder.forClient().build()); - httpClient = new AsyncRestTemplate(nettyFactory); + if (this.config.isUseSimpleClientHttpFactory()) { + httpClient = new AsyncRestTemplate(); + } else { + this.eventLoopGroup = new NioEventLoopGroup(); + Netty4ClientHttpRequestFactory nettyFactory = new Netty4ClientHttpRequestFactory(this.eventLoopGroup); + nettyFactory.setSslContext(SslContextBuilder.forClient().build()); + httpClient = new AsyncRestTemplate(nettyFactory); + } } catch (SSLException e) { throw new TbNodeException(e); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java index fb36ddb8af..6fa3f7151e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java @@ -27,6 +27,7 @@ public class TbRestApiCallNodeConfiguration implements NodeConfiguration headers; + private boolean useSimpleClientHttpFactory; @Override public TbRestApiCallNodeConfiguration defaultConfiguration() { @@ -34,6 +35,7 @@ public class TbRestApiCallNodeConfiguration implements NodeConfiguration
tb.rulenode.customer-name-pattern-required
tb.rulenode.customer-name-pattern-hint
{{ 'tb.rulenode.create-customer-if-not-exists' | translate }}
tb.rulenode.customer-cache-expiration-required
tb.rulenode.customer-cache-expiration-range
tb.rulenode.customer-cache-expiration-hint
"},function(e,t){e.exports='
{{scope.name | translate}}
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-details-function' | translate }}
tb.rulenode.alarm-type-required
"},function(e,t){e.exports="
{{ 'tb.rulenode.test-details-function' | translate }}
{{ 'tb.rulenode.use-message-alarm-data' | translate }}
tb.rulenode.alarm-type-required
{{ severity.name | translate}}
tb.rulenode.alarm-severity-required
{{ 'tb.rulenode.propagate' | translate }}
"},function(e,t){e.exports="
{{ ('relation.search-direction.' + direction) | translate}}
tb.rulenode.entity-name-pattern-required
tb.rulenode.entity-name-pattern-hint
tb.rulenode.entity-type-pattern-required
tb.rulenode.entity-type-pattern-hint
{{ 'tb.rulenode.create-entity-if-not-exists' | translate }}
tb.rulenode.entity-cache-expiration-required
tb.rulenode.entity-cache-expiration-range
tb.rulenode.entity-cache-expiration-hint
"},function(e,t){e.exports="
{{ 'tb.rulenode.delete-relation-to-specific-entity' | translate }}
tb.rulenode.delete-relation-hint
{{ ('relation.search-direction.' + direction) | translate}}
tb.rulenode.entity-name-pattern-required
tb.rulenode.entity-name-pattern-hint
tb.rulenode.entity-cache-expiration-required
tb.rulenode.entity-cache-expiration-range
tb.rulenode.entity-cache-expiration-hint
"},function(e,t){e.exports="
tb.rulenode.message-count-required
tb.rulenode.min-message-count-message
tb.rulenode.period-seconds-required
tb.rulenode.min-period-seconds-message
{{ 'tb.rulenode.test-generator-function' | translate }}
"},function(e,t){e.exports='
tb.rulenode.topic-pattern-required
tb.rulenode.bootstrap-servers-required
tb.rulenode.min-retries-message
tb.rulenode.min-batch-size-bytes-message
tb.rulenode.min-linger-ms-message
tb.rulenode.min-buffer-memory-bytes-message
{{ ackValue }}
tb.rulenode.key-serializer-required
tb.rulenode.value-serializer-required
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-to-string-function' | translate }}
"},function(e,t){e.exports='
tb.rulenode.topic-pattern-required
tb.rulenode.mqtt-topic-pattern-hint
tb.rulenode.host-required
tb.rulenode.port-required
tb.rulenode.port-range
tb.rulenode.port-range
tb.rulenode.connect-timeout-required
tb.rulenode.connect-timeout-range
tb.rulenode.connect-timeout-range
{{ \'tb.rulenode.clean-session\' | translate }} {{ \'tb.rulenode.enable-ssl\' | translate }}
{{ \'tb.rulenode.credentials\' | translate }}
{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}
{{ \'tb.rulenode.credentials\' | translate }}
{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}
{{credentialsValue.name | translate}}
tb.rulenode.credentials-type-required
tb.rulenode.username-required
tb.rulenode.password-required
'},function(e,t){e.exports="
tb.rulenode.interval-seconds-required
tb.rulenode.min-interval-seconds-message
tb.rulenode.output-timeseries-key-prefix-required
"},function(e,t){e.exports="
tb.rulenode.period-seconds-required
tb.rulenode.min-period-0-seconds-message
tb.rulenode.max-pending-messages-required
tb.rulenode.max-pending-messages-range
tb.rulenode.max-pending-messages-range
"},function(e,t){e.exports='
{{ property }}
tb.rulenode.host-required
tb.rulenode.port-required
tb.rulenode.port-range
tb.rulenode.port-range
{{ \'tb.rulenode.automatic-recovery\' | translate }}
tb.rulenode.min-connection-timeout-ms-message
tb.rulenode.min-handshake-timeout-ms-message
'},function(e,t){e.exports='
tb.rulenode.endpoint-url-pattern-required
tb.rulenode.endpoint-url-pattern-hint
{{ type }}
tb.rulenode.headers-hint
'},function(e,t){e.exports="
"},function(e,t){e.exports="
tb.rulenode.timeout-required
tb.rulenode.min-timeout-message
"},function(e,t){e.exports='
{{ \'tb.rulenode.use-system-smtp-settings\' | translate }}
{{smtpProtocol.toUpperCase()}}
tb.rulenode.smtp-host-required
tb.rulenode.smtp-port-required
tb.rulenode.smtp-port-range
tb.rulenode.smtp-port-range
tb.rulenode.timeout-required
tb.rulenode.min-timeout-msec-message
{{ \'tb.rulenode.enable-tls\' | translate }}
'; -},function(e,t){e.exports="
tb.rulenode.topic-arn-pattern-required
tb.rulenode.topic-arn-pattern-hint
tb.rulenode.aws-access-key-id-required
tb.rulenode.aws-secret-access-key-required
tb.rulenode.aws-region-required
"},function(e,t){e.exports='
{{ type.name | translate }}
tb.rulenode.queue-url-pattern-required
tb.rulenode.queue-url-pattern-hint
tb.rulenode.min-delay-seconds-message
tb.rulenode.max-delay-seconds-message
tb.rulenode.message-attributes-hint
tb.rulenode.aws-access-key-id-required
tb.rulenode.aws-secret-access-key-required
tb.rulenode.aws-region-required
'},function(e,t){e.exports="
tb.rulenode.default-ttl-required
tb.rulenode.min-default-ttl-message
"},function(e,t){e.exports="
tb.rulenode.customer-name-pattern-required
tb.rulenode.customer-name-pattern-hint
tb.rulenode.customer-cache-expiration-required
tb.rulenode.customer-cache-expiration-range
tb.rulenode.customer-cache-expiration-hint
"},function(e,t){e.exports='
{{ (\'relation.search-direction.\' + direction) | translate}}
relation.relation-type
device.device-types
'},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},function(e,t){e.exports='
'},function(e,t){e.exports='
{{ type }} {{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}
tb.rulenode.use-metadata-interval-patterns-hint
tb.rulenode.start-interval-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
tb.rulenode.end-interval-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
tb.rulenode.start-interval-pattern-required
tb.rulenode.start-interval-pattern-hint
tb.rulenode.end-interval-pattern-required
tb.rulenode.end-interval-pattern-hint
'},function(e,t){e.exports='
'},function(e,t){e.exports='
'},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},28,function(e,t){e.exports='
{{ \'tb.rulenode.check-all-keys\' | translate }}
tb.rulenode.check-all-keys-hint
'},function(e,t){e.exports="
{{ 'tb.rulenode.check-relation-to-specific-entity' | translate }}
tb.rulenode.check-relation-hint
{{ ('relation.search-direction.' + direction) | translate}}
"},function(e,t){e.exports='
{{item}}
tb.rulenode.no-message-types-found
tb.rulenode.no-message-type-matching tb.rulenode.create-new-message-type
{{$chip.name}}
'},function(e,t){e.exports='
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-filter-function' | translate }}
"},function(e,t){e.exports="
{{ 'tb.rulenode.test-switch-function' | translate }}
"},function(e,t){e.exports='
{{ keyText }} {{ valText }}  
{{keyRequiredText}}
{{valRequiredText}}
{{ \'tb.key-val.remove-entry\' | translate }} close
{{ \'tb.key-val.add-entry\' | translate }} add {{ \'action.add\' | translate }}
'},function(e,t){e.exports="
{{ ('relation.search-direction.' + direction) | translate}}
relation.relation-filters
"},function(e,t){e.exports='
{{ source.name | translate}}
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-transformer-function' | translate }}
"},function(e,t){e.exports="
tb.rulenode.from-template-required
tb.rulenode.from-template-hint
tb.rulenode.to-template-required
tb.rulenode.mail-address-list-template-hint
tb.rulenode.mail-address-list-template-hint
tb.rulenode.mail-address-list-template-hint
tb.rulenode.subject-template-required
tb.rulenode.subject-template-hint
tb.rulenode.body-template-required
tb.rulenode.body-template-hint
"},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(6),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(7),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n,a){var i=function(i,r,l,s){var u=o.default;r.html(u),i.types=n,i.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(i.configuration)}),s.$render=function(){i.configuration=s.$viewValue},i.testDetailsBuildJs=function(e){var n=angular.copy(i.configuration.alarmDetailsBuildJs);a.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],i.ruleNodeId).then(function(e){i.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(r.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}i.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(8),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n,a){var i=function(i,r,l,s){var u=o.default;r.html(u),i.types=n,i.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(i.configuration)}),s.$render=function(){i.configuration=s.$viewValue},i.testDetailsBuildJs=function(e){var n=angular.copy(i.configuration.alarmDetailsBuildJs);a.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],i.ruleNodeId).then(function(e){i.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(r.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}i.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(9),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(10),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(11),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n,a){var i=function(i,r,l,s){var u=o.default;r.html(u),i.types=n,i.originator=null,i.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(i.configuration); -}),s.$render=function(){i.configuration=s.$viewValue,i.configuration.originatorId&&i.configuration.originatorType?i.originator={id:i.configuration.originatorId,entityType:i.configuration.originatorType}:i.originator=null,i.$watch("originator",function(e,t){angular.equals(e,t)||(i.originator?(s.$viewValue.originatorId=i.originator.id,s.$viewValue.originatorType=i.originator.entityType):(s.$viewValue.originatorId=null,s.$viewValue.originatorType=null))},!0)},i.testScript=function(e){var n=angular.copy(i.configuration.jsScript);a.testNodeScript(e,n,"generate",t.instant("tb.rulenode.generator")+"","Generate",["prevMsg","prevMetadata","prevMsgType"],i.ruleNodeId).then(function(e){i.configuration.jsScript=e,s.$setDirty()})},e(r.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}i.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i,n(1);var r=n(12),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(66),r=a(i),o=n(47),l=a(o),s=n(52),u=a(s),d=n(49),c=a(d),m=n(48),g=a(m),p=n(55),f=a(p),b=n(61),v=a(b),y=n(62),h=a(y),q=n(60),$=a(q),k=n(54),x=a(k),T=n(64),C=a(T),w=n(65),M=a(w),_=n(59),S=a(_),N=n(56),E=a(N),V=n(63),P=a(V),F=n(58),j=a(F),A=n(57),O=a(A),I=n(46),K=a(I),D=n(67),R=a(D),U=n(51),L=a(U),z=n(50),B=a(z);t.default=angular.module("thingsboard.ruleChain.config.action",[]).directive("tbActionNodeTimeseriesConfig",r.default).directive("tbActionNodeAttributesConfig",l.default).directive("tbActionNodeGeneratorConfig",u.default).directive("tbActionNodeCreateAlarmConfig",c.default).directive("tbActionNodeClearAlarmConfig",g.default).directive("tbActionNodeLogConfig",f.default).directive("tbActionNodeRpcReplyConfig",v.default).directive("tbActionNodeRpcRequestConfig",h.default).directive("tbActionNodeRestApiCallConfig",$.default).directive("tbActionNodeKafkaConfig",x.default).directive("tbActionNodeSnsConfig",C.default).directive("tbActionNodeSqsConfig",M.default).directive("tbActionNodeRabbitMqConfig",S.default).directive("tbActionNodeMqttConfig",E.default).directive("tbActionNodeSendEmailConfig",P.default).directive("tbActionNodeMsgDelayConfig",j.default).directive("tbActionNodeMsgCountConfig",O.default).directive("tbActionNodeAssignToCustomerConfig",K.default).directive("tbActionNodeUnAssignToCustomerConfig",R.default).directive("tbActionNodeDeleteRelationConfig",L.default).directive("tbActionNodeCreateRelationConfig",B.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.ackValues=["all","-1","0","1"],t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(13),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n){var a=function(a,i,r,l){var s=o.default;i.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var i=angular.copy(a.configuration.jsScript);n.testNodeScript(e,i,"string",t.instant("tb.rulenode.to-string")+"","ToString",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}i.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(14),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n){var a=function(a,i,r,l){var s=o.default;i.html(s),a.$mdExpansionPanel=t,a.ruleNodeTypes=n,a.credentialsTypeChanged=function(){var e=a.configuration.credentials.type;a.configuration.credentials={},a.configuration.credentials.type=e,a.updateValidity()},a.certFileAdded=function(e,t){var n=new FileReader;n.onload=function(n){a.$apply(function(){if(n.target.result){l.$setDirty();var i=n.target.result;i&&i.length>0&&("caCert"==t&&(a.configuration.credentials.caCertFileName=e.name,a.configuration.credentials.caCert=i),"privateKey"==t&&(a.configuration.credentials.privateKeyFileName=e.name,a.configuration.credentials.privateKey=i),"Cert"==t&&(a.configuration.credentials.certFileName=e.name,a.configuration.credentials.cert=i)),a.updateValidity()}})},n.readAsText(e.file)},a.clearCertFile=function(e){l.$setDirty(),"caCert"==e&&(a.configuration.credentials.caCertFileName=null,a.configuration.credentials.caCert=null),"privateKey"==e&&(a.configuration.credentials.privateKeyFileName=null,a.configuration.credentials.privateKey=null),"Cert"==e&&(a.configuration.credentials.certFileName=null,a.configuration.credentials.cert=null),a.updateValidity()},a.updateValidity=function(){var e=!0,t=a.configuration.credentials;t.type==n.mqttCredentialTypes["cert.PEM"].value&&(t.caCert&&t.cert&&t.privateKey||(e=!1)),l.$setValidity("Certs",e)},a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:a}}i.$inject=["$compile","$mdExpansionPanel","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i,n(2);var r=n(15),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(16),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(17),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(18),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(19),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(20),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(21),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.smtpProtocols=["smtp","smtps"],t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(22),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(23),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}i.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(24),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(25),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(26),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||r.$setViewValue(n.query)}),r.$render=function(){n.query=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(27),o=a(r)},function(e,t){"use strict";function n(e){var t=function(t,n,a,i){n.html("
"),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(28),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(29),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n){var a=function(a,i,r,l){var s=o.default;i.html(s);var u=186;a.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,u],a.ruleNodeTypes=n,a.aggPeriodTimeUnits={},a.aggPeriodTimeUnits.MINUTES=n.timeUnit.MINUTES,a.aggPeriodTimeUnits.HOURS=n.timeUnit.HOURS,a.aggPeriodTimeUnits.DAYS=n.timeUnit.DAYS,a.aggPeriodTimeUnits.MILLISECONDS=n.timeUnit.MILLISECONDS,a.aggPeriodTimeUnits.SECONDS=n.timeUnit.SECONDS,a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{},link:a}}i.$inject=["$compile","$mdConstant","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(30),o=a(r);n(3)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(74),r=a(i),o=n(75),l=a(o),s=n(71),u=a(s),d=n(76),c=a(d),m=n(70),g=a(m),p=n(77),f=a(p),b=n(72),v=a(b);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",r.default).directive("tbEnrichmentNodeOriginatorFieldsConfig",l.default).directive("tbEnrichmentNodeDeviceAttributesConfig",u.default).directive("tbEnrichmentNodeRelatedAttributesConfig",c.default).directive("tbEnrichmentNodeCustomerAttributesConfig",g.default).directive("tbEnrichmentNodeTenantAttributesConfig",f.default).directive("tbEnrichmentNodeGetTelemetryFromDatabase",v.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(31),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(32),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(33),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(34),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(35),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(36),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(83),r=a(i),o=n(81),l=a(o),s=n(84),u=a(s),d=n(79),c=a(d),m=n(82),g=a(m),p=n(78),f=a(p);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",r.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",u.default).directive("tbFilterNodeCheckRelationConfig",c.default).directive("tbFilterNodeOriginatorTypeConfig",g.default).directive("tbFilterNodeCheckMessageConfig",f.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n){var a=function(a,i,r,l){function s(){if(l.$viewValue){for(var e=[],t=0;t-1&&t.kvList.splice(e,1)}function l(){t.kvList||(t.kvList=[]),t.kvList.push({key:"",value:""})}function s(){var e={};t.kvList.forEach(function(t){t.key&&(e[t.key]=t.value)}),i.$setViewValue(e),u()}function u(){var e=!0;t.required&&!t.kvList.length&&(e=!1),i.$setValidity("kvMap",e)}var d=o.default;n.html(d),t.ngModelCtrl=i,t.removeKeyVal=r,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||i.$setViewValue(t.query)}),i.$render=function(){if(i.$viewValue){var e=i.$viewValue;t.kvList.length=0;for(var n in e)t.kvList.push({key:n,value:e[n]})}t.$watch("kvList",function(e,t){angular.equals(e,t)||s()},!0),u()},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(41),o=a(r);n(5)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||r.$setViewValue(n.query)}),r.$render=function(){n.query=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(42),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(43),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(87),r=a(i),o=n(89),l=a(o),s=n(90),u=a(s);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",r.default).directive("tbTransformationNodeScriptConfig",l.default).directive("tbTransformationNodeToEmailConfig",u.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n){var a=function(a,i,r,l){var s=o.default;i.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var i=angular.copy(a.configuration.jsScript);n.testNodeScript(e,i,"update",t.instant("tb.rulenode.transformer")+"","Transform",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}i.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(44),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(45),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(94),r=a(i),o=n(80),l=a(o),s=n(73),u=a(s),d=n(88),c=a(d),m=n(53),g=a(m),p=n(69),f=a(p),b=n(86),v=a(b),y=n(68),h=a(y),q=n(85),$=a(q),k=n(93),x=a(k);t.default=angular.module("thingsboard.ruleChain.config",[r.default,l.default,u.default,c.default,g.default]).directive("tbNodeEmptyConfig",f.default).directive("tbRelationsQueryConfig",v.default).directive("tbDeviceRelationsQueryConfig",h.default).directive("tbKvMapConfig",$.default).config(x.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{"create-entity-if-not-exists":"Create new entity if not exists","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Time value should be in a range from 1 to 2147483647'.","start-interval-value-required":"Start interval value is required.","end-interval-value-required":"End interval value is required.",filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","latest-timeseries":"Latest timeseries","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required", -"mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","message-attributes":"Message attributes","message-attributes-hint":"Use ${metaKeyName} in name/value fields to substitute variables from metadata","connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"Certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","use-metadata-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys are present","check-all-keys-hint":"If selected checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity","check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-to-specific-entity":"Delete relation to specific entity","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","start-interval-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required."},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};e.translations("en_US",t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){(0,o.default)(e)}i.$inject=["$translateProvider"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(92),o=a(r)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{originatorSource:{CUSTOMER:{name:"tb.rulenode.originator-customer",value:"CUSTOMER"},TENANT:{name:"tb.rulenode.originator-tenant",value:"TENANT"},RELATED:{name:"tb.rulenode.originator-related",value:"RELATED"}},fetchModeType:["FIRST","LAST","ALL"],httpRequestType:["GET","POST","PUT","DELETE"],sqsQueueType:{STANDARD:{name:"tb.rulenode.sqs-queue-standard",value:"STANDARD"},FIFO:{name:"tb.rulenode.sqs-queue-fifo",value:"FIFO"}},timeUnit:{MILLISECONDS:{value:"MILLISECONDS",name:"tb.rulenode.time-unit-milliseconds"},SECONDS:{value:"SECONDS",name:"tb.rulenode.time-unit-seconds"},MINUTES:{value:"MINUTES",name:"tb.rulenode.time-unit-minutes"},HOURS:{value:"HOURS",name:"tb.rulenode.time-unit-hours"},DAYS:{value:"DAYS",name:"tb.rulenode.time-unit-days"}},mqttCredentialTypes:{anonymous:{value:"anonymous",name:"tb.rulenode.credentials-anonymous"},basic:{value:"basic",name:"tb.rulenode.credentials-basic"},"cert.PEM":{value:"cert.PEM",name:"tb.rulenode.credentials-pem"}}}).name}])); +!function(e){function t(a){if(n[a])return n[a].exports;var r=n[a]={exports:{},id:a,loaded:!1};return e[a].call(r.exports,r,r.exports,t),r.loaded=!0,r.exports}var n={};return t.m=e,t.c=n,t.p="/static/",t(0)}(function(e){for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t))switch(typeof e[t]){case"function":break;case"object":e[t]=function(t){var n=t.slice(1),a=e[t[0]];return function(e,t,r){a.apply(this,[e,t,r].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){e.exports=n(91)},function(e,t){},1,1,1,1,function(e,t){e.exports="
tb.rulenode.customer-name-pattern-required
tb.rulenode.customer-name-pattern-hint
{{ 'tb.rulenode.create-customer-if-not-exists' | translate }}
tb.rulenode.customer-cache-expiration-required
tb.rulenode.customer-cache-expiration-range
tb.rulenode.customer-cache-expiration-hint
"},function(e,t){e.exports='
{{scope.name | translate}}
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-details-function' | translate }}
tb.rulenode.alarm-type-required
tb.rulenode.entity-type-pattern-hint
"},function(e,t){e.exports="
{{ 'tb.rulenode.test-details-function' | translate }}
{{ 'tb.rulenode.use-message-alarm-data' | translate }}
tb.rulenode.alarm-type-required
{{ severity.name | translate}}
tb.rulenode.alarm-severity-required
{{ 'tb.rulenode.propagate' | translate }}
"},function(e,t){e.exports="
{{ ('relation.search-direction.' + direction) | translate}}
tb.rulenode.entity-name-pattern-required
tb.rulenode.entity-name-pattern-hint
tb.rulenode.entity-type-pattern-required
tb.rulenode.entity-type-pattern-hint
tb.rulenode.relation-type-pattern-required
tb.rulenode.relation-type-pattern-hint
{{ 'tb.rulenode.create-entity-if-not-exists' | translate }}
tb.rulenode.create-entity-if-not-exists-hint
{{ 'tb.rulenode.remove-current-relations' | translate }}
tb.rulenode.remove-current-relations-hint
{{ 'tb.rulenode.change-originator-to-related-entity' | translate }}
tb.rulenode.change-originator-to-related-entity-hint
tb.rulenode.entity-cache-expiration-required
tb.rulenode.entity-cache-expiration-range
tb.rulenode.entity-cache-expiration-hint
"},function(e,t){e.exports="
{{ 'tb.rulenode.delete-relation-to-specific-entity' | translate }}
tb.rulenode.delete-relation-hint
{{ ('relation.search-direction.' + direction) | translate}}
tb.rulenode.entity-name-pattern-required
tb.rulenode.entity-name-pattern-hint
tb.rulenode.relation-type-pattern-required
tb.rulenode.relation-type-pattern-hint
tb.rulenode.entity-cache-expiration-required
tb.rulenode.entity-cache-expiration-range
tb.rulenode.entity-cache-expiration-hint
"},function(e,t){e.exports="
tb.rulenode.message-count-required
tb.rulenode.min-message-count-message
tb.rulenode.period-seconds-required
tb.rulenode.min-period-seconds-message
{{ 'tb.rulenode.test-generator-function' | translate }}
"},function(e,t){e.exports='
tb.rulenode.topic-pattern-required
tb.rulenode.bootstrap-servers-required
tb.rulenode.min-retries-message
tb.rulenode.min-batch-size-bytes-message
tb.rulenode.min-linger-ms-message
tb.rulenode.min-buffer-memory-bytes-message
{{ ackValue }}
tb.rulenode.key-serializer-required
tb.rulenode.value-serializer-required
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-to-string-function' | translate }}
"},function(e,t){e.exports='
tb.rulenode.topic-pattern-required
tb.rulenode.mqtt-topic-pattern-hint
tb.rulenode.host-required
tb.rulenode.port-required
tb.rulenode.port-range
tb.rulenode.port-range
tb.rulenode.connect-timeout-required
tb.rulenode.connect-timeout-range
tb.rulenode.connect-timeout-range
{{ \'tb.rulenode.clean-session\' | translate }} {{ \'tb.rulenode.enable-ssl\' | translate }}
{{ \'tb.rulenode.credentials\' | translate }}
{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}
{{ \'tb.rulenode.credentials\' | translate }}
{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}
{{credentialsValue.name | translate}}
tb.rulenode.credentials-type-required
tb.rulenode.username-required
tb.rulenode.password-required
'},function(e,t){e.exports="
tb.rulenode.interval-seconds-required
tb.rulenode.min-interval-seconds-message
tb.rulenode.output-timeseries-key-prefix-required
"},function(e,t){e.exports="
tb.rulenode.period-seconds-required
tb.rulenode.min-period-0-seconds-message
tb.rulenode.max-pending-messages-required
tb.rulenode.max-pending-messages-range
tb.rulenode.max-pending-messages-range
"},function(e,t){e.exports='
{{ property }}
tb.rulenode.host-required
tb.rulenode.port-required
tb.rulenode.port-range
tb.rulenode.port-range
{{ \'tb.rulenode.automatic-recovery\' | translate }}
tb.rulenode.min-connection-timeout-ms-message
tb.rulenode.min-handshake-timeout-ms-message
'},function(e,t){e.exports='
tb.rulenode.endpoint-url-pattern-required
tb.rulenode.endpoint-url-pattern-hint
{{ type }} {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}
tb.rulenode.headers-hint
'; +},function(e,t){e.exports="
"},function(e,t){e.exports="
tb.rulenode.timeout-required
tb.rulenode.min-timeout-message
"},function(e,t){e.exports='
{{ \'tb.rulenode.use-system-smtp-settings\' | translate }}
{{smtpProtocol.toUpperCase()}}
tb.rulenode.smtp-host-required
tb.rulenode.smtp-port-required
tb.rulenode.smtp-port-range
tb.rulenode.smtp-port-range
tb.rulenode.timeout-required
tb.rulenode.min-timeout-msec-message
{{ \'tb.rulenode.enable-tls\' | translate }}
'},function(e,t){e.exports="
tb.rulenode.topic-arn-pattern-required
tb.rulenode.topic-arn-pattern-hint
tb.rulenode.aws-access-key-id-required
tb.rulenode.aws-secret-access-key-required
tb.rulenode.aws-region-required
"},function(e,t){e.exports='
{{ type.name | translate }}
tb.rulenode.queue-url-pattern-required
tb.rulenode.queue-url-pattern-hint
tb.rulenode.min-delay-seconds-message
tb.rulenode.max-delay-seconds-message
tb.rulenode.message-attributes-hint
tb.rulenode.aws-access-key-id-required
tb.rulenode.aws-secret-access-key-required
tb.rulenode.aws-region-required
'},function(e,t){e.exports="
tb.rulenode.default-ttl-required
tb.rulenode.min-default-ttl-message
"},function(e,t){e.exports="
tb.rulenode.customer-name-pattern-required
tb.rulenode.customer-name-pattern-hint
tb.rulenode.customer-cache-expiration-required
tb.rulenode.customer-cache-expiration-range
tb.rulenode.customer-cache-expiration-hint
"},function(e,t){e.exports='
{{ (\'relation.search-direction.\' + direction) | translate}}
relation.relation-type
device.device-types
'},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},function(e,t){e.exports='
'},function(e,t){e.exports='
{{ type }} {{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}
tb.rulenode.use-metadata-interval-patterns-hint
tb.rulenode.start-interval-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
tb.rulenode.end-interval-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
tb.rulenode.start-interval-pattern-required
tb.rulenode.start-interval-pattern-hint
tb.rulenode.end-interval-pattern-required
tb.rulenode.end-interval-pattern-hint
'},function(e,t){e.exports='
'},function(e,t){e.exports='
'},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},28,function(e,t){e.exports='
{{ \'tb.rulenode.check-all-keys\' | translate }}
tb.rulenode.check-all-keys-hint
'},function(e,t){e.exports="
{{ 'tb.rulenode.check-relation-to-specific-entity' | translate }}
tb.rulenode.check-relation-hint
{{ ('relation.search-direction.' + direction) | translate}}
"},function(e,t){e.exports='
{{item}}
tb.rulenode.no-message-types-found
tb.rulenode.no-message-type-matching tb.rulenode.create-new-message-type
{{$chip.name}}
'},function(e,t){e.exports='
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-filter-function' | translate }}
"},function(e,t){e.exports="
{{ 'tb.rulenode.test-switch-function' | translate }}
"},function(e,t){e.exports='
{{ keyText }} {{ valText }}  
{{keyRequiredText}}
{{valRequiredText}}
{{ \'tb.key-val.remove-entry\' | translate }} close
{{ \'tb.key-val.add-entry\' | translate }} add {{ \'action.add\' | translate }}
'},function(e,t){e.exports="
{{ ('relation.search-direction.' + direction) | translate}}
relation.relation-filters
"},function(e,t){e.exports='
{{ source.name | translate}}
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-transformer-function' | translate }}
"},function(e,t){e.exports="
tb.rulenode.from-template-required
tb.rulenode.from-template-hint
tb.rulenode.to-template-required
tb.rulenode.mail-address-list-template-hint
tb.rulenode.mail-address-list-template-hint
tb.rulenode.mail-address-list-template-hint
tb.rulenode.subject-template-required
tb.rulenode.subject-template-hint
tb.rulenode.body-template-required
tb.rulenode.body-template-hint
"},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n); +};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(6),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(7),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n,a){var r=function(r,i,l,s){var u=o.default;i.html(u),r.types=n,r.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(r.configuration)}),s.$render=function(){r.configuration=s.$viewValue},r.testDetailsBuildJs=function(e){var n=angular.copy(r.configuration.alarmDetailsBuildJs);a.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],r.ruleNodeId).then(function(e){r.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(i.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:r}}r.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(8),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n,a){var r=function(r,i,l,s){var u=o.default;i.html(u),r.types=n,r.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(r.configuration)}),s.$render=function(){r.configuration=s.$viewValue},r.testDetailsBuildJs=function(e){var n=angular.copy(r.configuration.alarmDetailsBuildJs);a.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],r.ruleNodeId).then(function(e){r.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(i.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:r}}r.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(9),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(10),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(11),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n,a){var r=function(r,i,l,s){var u=o.default;i.html(u),r.types=n,r.originator=null,r.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(r.configuration)}),s.$render=function(){r.configuration=s.$viewValue,r.configuration.originatorId&&r.configuration.originatorType?r.originator={id:r.configuration.originatorId,entityType:r.configuration.originatorType}:r.originator=null,r.$watch("originator",function(e,t){angular.equals(e,t)||(r.originator?(s.$viewValue.originatorId=r.originator.id,s.$viewValue.originatorType=r.originator.entityType):(s.$viewValue.originatorId=null,s.$viewValue.originatorType=null))},!0)},r.testScript=function(e){var n=angular.copy(r.configuration.jsScript);a.testNodeScript(e,n,"generate",t.instant("tb.rulenode.generator")+"","Generate",["prevMsg","prevMetadata","prevMsgType"],r.ruleNodeId).then(function(e){r.configuration.jsScript=e,s.$setDirty()})},e(i.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:r}}r.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r,n(1);var i=n(12),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(66),i=a(r),o=n(47),l=a(o),s=n(52),u=a(s),d=n(49),c=a(d),m=n(48),g=a(m),p=n(55),f=a(p),b=n(61),v=a(b),y=n(62),h=a(y),q=n(60),$=a(q),k=n(54),x=a(k),T=n(64),C=a(T),w=n(65),M=a(w),_=n(59),S=a(_),N=n(56),E=a(N),P=n(63),V=a(P),F=n(58),j=a(F),A=n(57),O=a(A),I=n(46),R=a(I),K=n(67),D=a(K),U=n(51),L=a(U),z=n(50),B=a(z);t.default=angular.module("thingsboard.ruleChain.config.action",[]).directive("tbActionNodeTimeseriesConfig",i.default).directive("tbActionNodeAttributesConfig",l.default).directive("tbActionNodeGeneratorConfig",u.default).directive("tbActionNodeCreateAlarmConfig",c.default).directive("tbActionNodeClearAlarmConfig",g.default).directive("tbActionNodeLogConfig",f.default).directive("tbActionNodeRpcReplyConfig",v.default).directive("tbActionNodeRpcRequestConfig",h.default).directive("tbActionNodeRestApiCallConfig",$.default).directive("tbActionNodeKafkaConfig",x.default).directive("tbActionNodeSnsConfig",C.default).directive("tbActionNodeSqsConfig",M.default).directive("tbActionNodeRabbitMqConfig",S.default).directive("tbActionNodeMqttConfig",E.default).directive("tbActionNodeSendEmailConfig",V.default).directive("tbActionNodeMsgDelayConfig",j.default).directive("tbActionNodeMsgCountConfig",O.default).directive("tbActionNodeAssignToCustomerConfig",R.default).directive("tbActionNodeUnAssignToCustomerConfig",D.default).directive("tbActionNodeDeleteRelationConfig",L.default).directive("tbActionNodeCreateRelationConfig",B.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.ackValues=["all","-1","0","1"],t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(13),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var r=angular.copy(a.configuration.jsScript);n.testNodeScript(e,r,"string",t.instant("tb.rulenode.to-string")+"","ToString",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(14),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$mdExpansionPanel=t,a.ruleNodeTypes=n,a.credentialsTypeChanged=function(){var e=a.configuration.credentials.type;a.configuration.credentials={},a.configuration.credentials.type=e,a.updateValidity()},a.certFileAdded=function(e,t){var n=new FileReader;n.onload=function(n){a.$apply(function(){if(n.target.result){l.$setDirty();var r=n.target.result;r&&r.length>0&&("caCert"==t&&(a.configuration.credentials.caCertFileName=e.name,a.configuration.credentials.caCert=r),"privateKey"==t&&(a.configuration.credentials.privateKeyFileName=e.name,a.configuration.credentials.privateKey=r),"Cert"==t&&(a.configuration.credentials.certFileName=e.name,a.configuration.credentials.cert=r)),a.updateValidity()}})},n.readAsText(e.file)},a.clearCertFile=function(e){l.$setDirty(),"caCert"==e&&(a.configuration.credentials.caCertFileName=null,a.configuration.credentials.caCert=null),"privateKey"==e&&(a.configuration.credentials.privateKeyFileName=null,a.configuration.credentials.privateKey=null),"Cert"==e&&(a.configuration.credentials.certFileName=null,a.configuration.credentials.cert=null),a.updateValidity()},a.updateValidity=function(){var e=!0,t=a.configuration.credentials;t.type==n.mqttCredentialTypes["cert.PEM"].value&&(t.caCert&&t.cert&&t.privateKey||(e=!1)),l.$setValidity("Certs",e)},a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:a}}r.$inject=["$compile","$mdExpansionPanel","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r,n(2);var i=n(15),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(16),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(17),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(18),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}r.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(19),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(20),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(21),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.smtpProtocols=["smtp","smtps"],t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(22),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(23),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}r.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(24),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(25),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(26),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||i.$setViewValue(n.query)}),i.$render=function(){n.query=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(27),o=a(i)},function(e,t){"use strict";function n(e){var t=function(t,n,a,r){n.html("
"),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(28),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(29),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s);var u=186;a.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,u],a.ruleNodeTypes=n,a.aggPeriodTimeUnits={},a.aggPeriodTimeUnits.MINUTES=n.timeUnit.MINUTES,a.aggPeriodTimeUnits.HOURS=n.timeUnit.HOURS,a.aggPeriodTimeUnits.DAYS=n.timeUnit.DAYS,a.aggPeriodTimeUnits.MILLISECONDS=n.timeUnit.MILLISECONDS,a.aggPeriodTimeUnits.SECONDS=n.timeUnit.SECONDS,a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{},link:a}}r.$inject=["$compile","$mdConstant","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(30),o=a(i);n(3)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(74),i=a(r),o=n(75),l=a(o),s=n(71),u=a(s),d=n(76),c=a(d),m=n(70),g=a(m),p=n(77),f=a(p),b=n(72),v=a(b);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",i.default).directive("tbEnrichmentNodeOriginatorFieldsConfig",l.default).directive("tbEnrichmentNodeDeviceAttributesConfig",u.default).directive("tbEnrichmentNodeRelatedAttributesConfig",c.default).directive("tbEnrichmentNodeCustomerAttributesConfig",g.default).directive("tbEnrichmentNodeTenantAttributesConfig",f.default).directive("tbEnrichmentNodeGetTelemetryFromDatabase",v.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(31),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(32),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(33),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(34),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(35),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(36),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(83),i=a(r),o=n(81),l=a(o),s=n(84),u=a(s),d=n(79),c=a(d),m=n(82),g=a(m),p=n(78),f=a(p);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",i.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",u.default).directive("tbFilterNodeCheckRelationConfig",c.default).directive("tbFilterNodeOriginatorTypeConfig",g.default).directive("tbFilterNodeCheckMessageConfig",f.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){function s(){if(l.$viewValue){for(var e=[],t=0;t-1&&t.kvList.splice(e,1)}function l(){t.kvList||(t.kvList=[]),t.kvList.push({key:"",value:""})}function s(){var e={};t.kvList.forEach(function(t){t.key&&(e[t.key]=t.value)}),r.$setViewValue(e),u()}function u(){var e=!0;t.required&&!t.kvList.length&&(e=!1),r.$setValidity("kvMap",e)}var d=o.default;n.html(d),t.ngModelCtrl=r,t.removeKeyVal=i,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||r.$setViewValue(t.query)}),r.$render=function(){if(r.$viewValue){var e=r.$viewValue;t.kvList.length=0;for(var n in e)t.kvList.push({key:n,value:e[n]})}t.$watch("kvList",function(e,t){angular.equals(e,t)||s()},!0),u()},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(41),o=a(i);n(5)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||i.$setViewValue(n.query)}),i.$render=function(){n.query=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(42),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(43),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(87),i=a(r),o=n(89),l=a(o),s=n(90),u=a(s);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",i.default).directive("tbTransformationNodeScriptConfig",l.default).directive("tbTransformationNodeToEmailConfig",u.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var r=angular.copy(a.configuration.jsScript);n.testNodeScript(e,r,"update",t.instant("tb.rulenode.transformer")+"","Transform",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(44),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(45),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(94),i=a(r),o=n(80),l=a(o),s=n(73),u=a(s),d=n(88),c=a(d),m=n(53),g=a(m),p=n(69),f=a(p),b=n(86),v=a(b),y=n(68),h=a(y),q=n(85),$=a(q),k=n(93),x=a(k);t.default=angular.module("thingsboard.ruleChain.config",[i.default,l.default,u.default,c.default,g.default]).directive("tbNodeEmptyConfig",f.default).directive("tbRelationsQueryConfig",v.default).directive("tbDeviceRelationsQueryConfig",h.default).directive("tbKvMapConfig",$.default).config(x.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{"create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit", +"end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Time value should be in a range from 1 to 2147483647'.","start-interval-value-required":"Start interval value is required.","end-interval-value-required":"End interval value is required.",filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","latest-timeseries":"Latest timeseries","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","relation-type-pattern-required":"Relation type pattern is required","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","message-attributes":"Message attributes","message-attributes-hint":"Use ${metaKeyName} in name/value fields to substitute variables from metadata","connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"Certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","use-metadata-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys are present","check-all-keys-hint":"If selected checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity","check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-to-specific-entity":"Delete relation to specific entity","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","remove-current-relations":"Remove current relations","remove-current-relations-hint":"Removes current relations from the originator of the incoming message based on direction and type.","change-originator-to-related-entity":"Change originator to related entity","change-originator-to-related-entity-hint":"Used to process submitted message as a message from another entity.","start-interval-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required."},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};e.translations("en_US",t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){(0,o.default)(e)}r.$inject=["$translateProvider"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(92),o=a(i)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{originatorSource:{CUSTOMER:{name:"tb.rulenode.originator-customer",value:"CUSTOMER"},TENANT:{name:"tb.rulenode.originator-tenant",value:"TENANT"},RELATED:{name:"tb.rulenode.originator-related",value:"RELATED"}},fetchModeType:["FIRST","LAST","ALL"],httpRequestType:["GET","POST","PUT","DELETE"],sqsQueueType:{STANDARD:{name:"tb.rulenode.sqs-queue-standard",value:"STANDARD"},FIFO:{name:"tb.rulenode.sqs-queue-fifo",value:"FIFO"}},timeUnit:{MILLISECONDS:{value:"MILLISECONDS",name:"tb.rulenode.time-unit-milliseconds"},SECONDS:{value:"SECONDS",name:"tb.rulenode.time-unit-seconds"},MINUTES:{value:"MINUTES",name:"tb.rulenode.time-unit-minutes"},HOURS:{value:"HOURS",name:"tb.rulenode.time-unit-hours"},DAYS:{value:"DAYS",name:"tb.rulenode.time-unit-days"}},mqttCredentialTypes:{anonymous:{value:"anonymous",name:"tb.rulenode.credentials-anonymous"},basic:{value:"basic",name:"tb.rulenode.credentials-basic"},"cert.PEM":{value:"cert.PEM",name:"tb.rulenode.credentials-pem"}}}).name}])); //# sourceMappingURL=rulenode-core-config.js.map \ No newline at end of file diff --git a/tools/pom.xml b/tools/pom.xml index 211ae8dab2..6da6c2fdc1 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.2.1-SNAPSHOT + 2.3.0 thingsboard tools diff --git a/transport/coap/pom.xml b/transport/coap/pom.xml index 3a75ae593b..05088b5bc6 100644 --- a/transport/coap/pom.xml +++ b/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.2.1-SNAPSHOT + 2.3.0 transport org.thingsboard.transport diff --git a/transport/http/pom.xml b/transport/http/pom.xml index d2b4465f9f..d3e5cc47b4 100644 --- a/transport/http/pom.xml +++ b/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.2.1-SNAPSHOT + 2.3.0 transport org.thingsboard.transport diff --git a/transport/mqtt/pom.xml b/transport/mqtt/pom.xml index d896a68249..cc0628e399 100644 --- a/transport/mqtt/pom.xml +++ b/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.2.1-SNAPSHOT + 2.3.0 transport org.thingsboard.transport diff --git a/transport/pom.xml b/transport/pom.xml index 1fd1d78c5a..4a70402d66 100644 --- a/transport/pom.xml +++ b/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.2.1-SNAPSHOT + 2.3.0 thingsboard transport diff --git a/ui/package-lock.json b/ui/package-lock.json index c984723fe4..30fd603198 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -1,6 +1,6 @@ { "name": "thingsboard", - "version": "2.2.1", + "version": "2.3.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/ui/package.json b/ui/package.json index a9ec3e0d6c..7345ae6425 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,7 +1,7 @@ { "name": "thingsboard", "private": true, - "version": "2.2.1", + "version": "2.3.0", "description": "ThingsBoard UI", "licenses": [ { diff --git a/ui/pom.xml b/ui/pom.xml index 93a2d0b1bb..d5f91db026 100644 --- a/ui/pom.xml +++ b/ui/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.2.1-SNAPSHOT + 2.3.0 thingsboard org.thingsboard diff --git a/ui/src/app/entity/entity-subtype-autocomplete.directive.js b/ui/src/app/entity/entity-subtype-autocomplete.directive.js index b1959afe8b..213b4020c9 100644 --- a/ui/src/app/entity/entity-subtype-autocomplete.directive.js +++ b/ui/src/app/entity/entity-subtype-autocomplete.directive.js @@ -40,7 +40,7 @@ export default function EntitySubtypeAutocomplete($compile, $templateCache, $q, if ((actual === null) || (expected === null)) { return actual === expected; } - return actual.indexOf(expected) !== -1; + return actual.startsWith(expected); }; scope.fetchSubTypes = function(searchText) { @@ -49,6 +49,10 @@ export default function EntitySubtypeAutocomplete($compile, $templateCache, $q, function success(subTypes) { var result = $filter('filter')(subTypes, {'$': searchText}, comparator); if (result && result.length) { + if (searchText && searchText.length && result.indexOf(searchText) === -1) { + result.push(searchText); + } + result.sort(); deferred.resolve(result); } else { deferred.resolve([searchText]); @@ -62,7 +66,7 @@ export default function EntitySubtypeAutocomplete($compile, $templateCache, $q, } scope.subTypeSearchTextChanged = function() { - scope.subType = scope.subTypeSearchText; + //scope.subType = scope.subTypeSearchText; } scope.updateView = function () { diff --git a/ui/src/app/entity/entity-subtype-autocomplete.tpl.html b/ui/src/app/entity/entity-subtype-autocomplete.tpl.html index 987cc7f46a..f46994efe1 100644 --- a/ui/src/app/entity/entity-subtype-autocomplete.tpl.html +++ b/ui/src/app/entity/entity-subtype-autocomplete.tpl.html @@ -29,6 +29,8 @@ placeholder="{{ selectEntitySubtypeText | translate }}" md-floating-label="{{ entitySubtypeText | translate }}" md-select-on-match="false" + md-autoselect="true" + ng-blur="subType = subTypeSearchText" md-menu-class="tb-entity-subtype-autocomplete">
diff --git a/ui/src/app/locale/locale.constant-de_DE.json b/ui/src/app/locale/locale.constant-de_DE.json index dd462a383f..bc241e28a5 100644 --- a/ui/src/app/locale/locale.constant-de_DE.json +++ b/ui/src/app/locale/locale.constant-de_DE.json @@ -1559,6 +1559,7 @@ "widget-action": { "action-cell-button": "Aktionszellenschaltfläche", "row-click": "Klick auf Zeile", + "polygon-click": "Klick auf Polygon", "marker-click": "Klick auf Marker", "tooltip-tag-action": "Tooltip-Tag-Aktion" } diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index f8b4b3d4f2..9e38c55aaa 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -1564,6 +1564,7 @@ "widget-action": { "action-cell-button": "Action cell button", "row-click": "On row click", + "polygon-click": "On polygon click", "marker-click": "On marker click", "tooltip-tag-action": "Tooltip tag action" } diff --git a/ui/src/app/locale/locale.constant-es_ES.json b/ui/src/app/locale/locale.constant-es_ES.json index afb96ceb0c..48ace08e6b 100644 --- a/ui/src/app/locale/locale.constant-es_ES.json +++ b/ui/src/app/locale/locale.constant-es_ES.json @@ -1559,7 +1559,8 @@ "widget-action": { "action-cell-button": "Botón de acción de celda", "row-click": "Clic en la fila", - "marker-click": "Clic en el marcador", + "polygon-click": "Clic en la fila", + "marker-click": "Clic en el polígono", "tooltip-tag-action": "Acción de etiqueta para globo de ayuda" } }, diff --git a/ui/src/app/locale/locale.constant-fr_FR.json b/ui/src/app/locale/locale.constant-fr_FR.json index 651b695779..f22f90548b 100644 --- a/ui/src/app/locale/locale.constant-fr_FR.json +++ b/ui/src/app/locale/locale.constant-fr_FR.json @@ -336,6 +336,7 @@ "action-cell-button": "Action cell button", "marker-click": "On marker click", "row-click": "On row click", + "polygon-click": "On polygon click", "tooltip-tag-action": "Tooltip tag action" } }, diff --git a/ui/src/app/locale/locale.constant-it_IT.json b/ui/src/app/locale/locale.constant-it_IT.json index 1d3a84fe25..986fdc19a0 100644 --- a/ui/src/app/locale/locale.constant-it_IT.json +++ b/ui/src/app/locale/locale.constant-it_IT.json @@ -133,8 +133,13 @@ "min-polling-interval-message": "L'intervallo di polling deve essere di almeno 1 sec.", "aknowledge-alarms-title": "Conferma { count, plural, 1 {1 allarme} other {# allarmi} }", "aknowledge-alarms-text": "Sei sicuro di voler confermare { count, plural, 1 {1 allarme} other {# allarmi} }?", + "aknowledge-alarm-title": "Conferma allarme", + "aknowledge-alarm-text": "Sei sicuro di voler confermare l'allarme?", "clear-alarms-title": "Elimina { count, plural, 1 {1 allarme} other {# allarmi} }", - "clear-alarms-text": "Sei sicuro di voler eliminare { count, plural, 1 {1 allarme} other {# allarmi} }?" + "clear-alarms-text": "Sei sicuro di voler eliminare { count, plural, 1 {1 allarme} other {# allarmi} }?", + "clear-alarm-title": "Elimina allarme", + "clear-alarm-text": "Sei sicuro di voler eliminare l'allarme?", + "alarm-status-filter": "Filtro stato allarme" }, "alias": { "add": "Aggiungi alias", @@ -145,7 +150,7 @@ "filter-type-single-entity": "Singola entità", "filter-type-entity-list": "Lista Entità", "filter-type-entity-name": "Nome Entità", - "filter-type-state-entity": "Entity from dashboard state", + "filter-type-state-entity": "Entità dallo stato della dashboard", "filter-type-state-entity-description": "Entità prelevata dai parametri di stato della dashboard", "filter-type-asset-type": "Tipo di Asset", "filter-type-asset-type-description": "Asset di tipo '{{assetType}}'", @@ -153,26 +158,31 @@ "filter-type-device-type": "Tipo di dispositivo", "filter-type-device-type-description": "Dispositivi di tipo '{{deviceType}}'", "filter-type-device-type-and-name-description": "Dispositivi di tipo '{{deviceType}}' e con un nome che inizia per '{{prefix}}'", - "filter-type-relations-query": "Relations query", - "filter-type-relations-query-description": "{{entities}} that have {{relationType}} relation {{direction}} {{rootEntity}}", + "filter-type-entity-view-type": "Tipo vista entità", + "filter-type-entity-view-type-description": "Viste entità di tipo '{{entityView}}'", + "filter-type-entity-view-type-and-name-description": "Viste entità di tipo '{{entityView}}' e con un nome che inizia per '{{prefix}}'", + "filter-type-relations-query": "Query relazioni", + "filter-type-relations-query-description": "{{entities}} che hanno una relazione {{relationType}} {{direction}} {{rootEntity}}", "filter-type-asset-search-query": "Query ricerca asset", - "filter-type-asset-search-query-description": "Assets with types {{assetTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}", + "filter-type-asset-search-query-description": "Asset di tipo {{assetTypes}} che hanno una relazione {{relationType}} {{direction}} {{rootEntity}}", "filter-type-device-search-query": "Query ricerca dispositivo", - "filter-type-device-search-query-description": "Devices with types {{deviceTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}", + "filter-type-device-search-query-description": "Dispositivi di tipo {{deviceTypes}} che hanno una relazione {{relationType}} {{direction}} {{rootEntity}}", + "filter-type-entity-view-search-query": "Query ricerca Vista entità", + "filter-type-entity-view-search-query-description": "Viste entità di tipo {{entityViewTypes}} che hanno una relazione {{relationType}} {{direction}} {{rootEntity}}", "entity-filter": "Filtro entità", - "resolve-multiple": "Resolve as multiple entities", + "resolve-multiple": "Risolvi come entità multiple", "filter-type": "Tipo di filtro", "filter-type-required": "Tipo di filtro richiesto.", "entity-filter-no-entity-matched": "Nessuna entità corrispondente al filtro specificato è stata trovata.", "no-entity-filter-specified": "Nessun filtro di entità specificato", - "root-state-entity": "Use dashboard state entity as root", + "root-state-entity": "Usa l'entità di stato della dashboard come radice", "root-entity": "Entità radice", - "state-entity-parameter-name": "State entity parameter name", - "default-state-entity": "Default state entity", - "default-entity-parameter-name": "By default", - "max-relation-level": "Max relation level", - "unlimited-level": "Unlimited level", - "state-entity": "Dashboard state entity", + "state-entity-parameter-name": "Nome parametro entità di stato", + "default-state-entity": "Entità di stato predefinita", + "default-entity-parameter-name": "Predefinito", + "max-relation-level": "Massimo livello relazione", + "unlimited-level": "Illimitato", + "state-entity": "Entità di stato della dashboard", "all-entities": "Tutte le entità", "any-relation": "qualsiasi" }, @@ -212,10 +222,10 @@ "add-asset-text": "Aggiungi un nuovo asset", "asset-details": "Dettagli Asset", "assign-assets": "Assegna asset", - "assign-assets-text": "Assegna { count, plural, 1 {1 asset} other {# assets} } al cliente", + "assign-assets-text": "Assegna { count, plural, 1 {1 asset} other {# asset} } al cliente", "delete-assets": "Cancella asset", "unassign-assets": "Annulla assegnazione asset", - "unassign-assets-action-title": "Unassign { count, plural, 1 {1 asset} other {# assets} } from customer", + "unassign-assets-action-title": "Annulla assegnazione { count, plural, 1 {1 asset} other {# asset} } al cliente", "assign-new-asset": "Assegna un nuovo asset", "delete-asset-title": "Sei sicuro di voler cancellare l'asset '{{assetName}}'?", "delete-asset-text": "Attenzione, dopo la conferma l'asset e tutti i relativi dati non saranno più recuperabili.", @@ -234,7 +244,7 @@ "copyId": "Copia Id asset", "idCopiedMessage": "Id Asset copiato negli Appunti", "select-asset": "Seleziona asset", - "no-assets-matching": "Nessun asset corrispondente a '{{entity}}' é stato trovato.", + "no-assets-matching": "Nessun asset corrispondente a '{{entity}}' è stato trovato.", "asset-required": "Asset obbligatorio", "name-starts-with": "Asset con nome che inizia per" }, @@ -288,12 +298,17 @@ "type-suspended": "Sospeso", "type-credentials-read": "Credenziali lette", "type-attributes-read": "Attributi letti", - "status-success": "Success", - "status-failure": "Failure", + "type-relation-add-or-update": "Relazione aggiornata", + "type-relation-delete": "Relazione eliminata", + "type-relations-delete": "Eliminate tutte le relazioni", + "type-alarm-ack": "Confermato", + "type-alarm-clear": "Eliminato", + "status-success": "Successo", + "status-failure": "Fallito", "audit-log-details": "Dettaglio log audit", "no-audit-logs-prompt": "Log non trovati", "action-data": "Action data", - "failure-details": "Failure details", + "failure-details": "Dettagli fallimento", "search": "Cerca log audit", "clear-search": "Cancella ricerca" }, @@ -333,10 +348,12 @@ "dashboard": "Dashboard cliente", "dashboards": "Dashboard cliente", "devices": "Dispositivi cliente", + "entity-views": "Viste entità cliente", "assets": "Asset cliente", "public-dashboards": "Dashboard pubbliche", "public-devices": "Dispositivi pubblici", "public-assets": "Asset pubblici", + "public-entity-views": "Viste entità pubbliche", "add": "Aggiungi cliente", "delete": "Elimina cliente", "manage-customer-users": "Gestisci utenti cliente", @@ -388,14 +405,14 @@ "assign-dashboard-to-customer-text": "Seleziona le dashboard da assegnare al client", "assign-to-customer-text": "Seleziona il cliente a cui assegnare la/le dashboard", "assign-to-customer": "Assegna al cliente", - "unassign-from-customer": "Unassign from customer", + "unassign-from-customer": "Annulla assegnazione al cliente", "make-public": "Rendi pubblica la dashboard", "make-private": "Rendi privata la dashboard", "manage-assigned-customers": "Gestisci clienti assegnati", "assigned-customers": "Clienti assegnati", "assign-to-customers": "Assegna Dashboard ai Clienti", "assign-to-customers-text": "Seleziona i clienti da assegnare alla/alle dashboard", - "unassign-from-customers": "Unassign Dashboard(s) From Customers", + "unassign-from-customers": "Annulla assegnazione Dashboard ai Clienti", "unassign-from-customers-text": "Seleziona i clienti di cui annullare l'assegnazione alla/alle dashboard", "no-dashboards-text": "Nessuna dashboard trovata", "no-widgets": "Nessun widget configurato", @@ -412,10 +429,10 @@ "assign-dashboards": "Assegna dashboard", "assign-new-dashboard": "Assegna nuova dashboard", "assign-dashboards-text": "Assegna { count, plural, 1 {1 dashboard} other {# dashboard} } ai clienti", - "unassign-dashboards-action-text": "Annulla assegnazione { count, plural, 1 {1 dashboard} other {# dashboards} } ai clienti", + "unassign-dashboards-action-text": "Annulla assegnazione { count, plural, 1 {1 dashboard} other {# dashboard} } ai clienti", "delete-dashboards": "Elimina dashboard", "unassign-dashboards": "Annulla assegnazione dashboard", - "unassign-dashboards-action-title": "Annulla assegnazione { count, plural, 1 {1 dashboard} other {# dashboards} } al cliente", + "unassign-dashboards-action-title": "Annulla assegnazione { count, plural, 1 {1 dashboard} other {# dashboard} } al cliente", "delete-dashboard-title": "Sei sicuro di voler cancellare la dashboard '{{dashboardTitle}}'?", "delete-dashboard-text": "Attenzione, dopo la conferma la dashboard e tutti i suoi dati non saranno più recuperabili.", "delete-dashboards-title": "Sei sicuro di voler eliminare { count, plural, 1 {1 dashboard} other {# dashboard} }?", @@ -425,7 +442,7 @@ "unassign-dashboard-text": "Dopo la conferma sarà annullata l'assegnazione della dashboard e questa non sarà più accessibile dal cliente.", "unassign-dashboard": "Annulla assegnazione dashboard", "unassign-dashboards-title": "Sei sicuro di voler annullare l'assegnazione di { count, plural, 1 {1 dashboard} other {# dashboard} }?", - "unassign-dashboards-text": "Dopo la conferma sarà annullata l'assegnazione di tutte le dashboards selezionate e queste non saranno più accessibili dal cliente.", + "unassign-dashboards-text": "Dopo la conferma sarà annullata l'assegnazione di tutte le dashboard selezionate e queste non saranno più accessibili dal cliente.", "public-dashboard-title": "La Dashboard è ora pubblica", "public-dashboard-text": "La dashboard {{dashboardTitle}} è ora pubblica e accessibile al link:", "public-dashboard-notice": "Nota: Ricorda di rendere pubblici i relativi dispositivi per accedere ai loro dati.", @@ -461,12 +478,12 @@ "vertical-margin-required": "Margine verticale obbligatorio.", "min-vertical-margin-message": "Ammesso un margine verticale minimo pari a 0.", "max-vertical-margin-message": "Ammesso un margine verticale massimo pari a 50.", - "autofill-height": "Auto fill layout height", + "autofill-height": "Riempi automaticamente altezza layout", "mobile-layout": "Impostazioni layout mobile", - "mobile-row-height": "Mobile row height, px", - "mobile-row-height-required": "Mobile row height value is required.", - "min-mobile-row-height-message": "Only 5 pixels is allowed as minimum mobile row height value.", - "max-mobile-row-height-message": "Only 200 pixels is allowed as maximum mobile row height value.", + "mobile-row-height": "Altezza riga mobile (px)", + "mobile-row-height-required": "Altezza riga mobile è richiesta.", + "min-mobile-row-height-message": "5 pixel è il minimo concesso al valore altezza riga mobile.", + "max-mobile-row-height-message": "200 pixel è il massimo concesso al valore altezza riga mobile.", "display-title": "Mostra titolo dashboard", "toolbar-always-open": "Mantieni aperta la barra degli strumenti", "title-color": "Colore titolo", @@ -527,18 +544,23 @@ "units": "Simbolo speciale da mostrare accanto al valore", "decimals": "Numero cifre decimali", "data-generation-func": "Funzione generazione dati", - "use-data-post-processing-func": "Use data post-processing function", + "use-data-post-processing-func": "Usa funzione dopo il processamento dei dati", "configuration": "Configurazione data key", "timeseries": "Serie temporali", "attributes": "Attributi", "alarm": "Campi allarme", - "timeseries-required": "Entity timeseries are required.", - "timeseries-or-attributes-required": "Entity timeseries/attributes are required.", + "timeseries-required": "Le serie temporali dell'entità sono richieste.", + "timeseries-or-attributes-required": "Le serie temporali o gli attributi dell'entità sono richiesti.", "maximum-timeseries-or-attributes": "Massimo { count, plural, 1 {1 serie temporale/attributo consentito.} other {# serie temporali/attributi consentiti.} }", "alarm-fields-required": "Campi allarme obbligatori.", "function-types": "Tipi funzione", "function-types-required": "Tipi funzione obbligatorio.", - "maximum-function-types": "Massimo { count, plural, 1 {1 tipo di funzione consentito.} other {# tipi di funzione consentiti} }" + "maximum-function-types": "Massimo { count, plural, 1 {1 tipo di funzione consentito.} other {# tipi di funzione consentiti} }", + "time-description": "timestamp del valore corrente;", + "value-description": "il valore corrente;", + "prev-value-description": "risultato della precedente chiamata alla funzione;", + "time-prev-description": "timestamp del valore precedente;", + "prev-orig-value-description": "valore precedente originale;" }, "datasource": { "type": "Tipo sorgente dati", @@ -563,11 +585,11 @@ "no-keys-found": "Nessuna chiave trovata.", "create-new-alias": "Creane uno nuovo!", "create-new-key": "Creane una nuova!", - "duplicate-alias-error": "Sono stati trovati dei duplicati dell'alias '{{alias}}'.
Gli alias di un dispositivo devono essere univoci all'interno della dashboard.", + "duplicate-alias-error": "Sono stati trovati dei duplicati dell'alias '{{alias}}'.
Gli alias di un dispositivo devono essere univoci all'interno della dashboard.", "configure-alias": "Configura alias '{{alias}}'", - "no-devices-matching": "Nessun dispositivo corrispondente a '{{entity}}' é stato trovato.", + "no-devices-matching": "Nessun dispositivo corrispondente a '{{entity}}' è stato trovato.", "alias": "Alias", - "alias-required": "Alias dispositivo richesto.", + "alias-required": "Alias dispositivo richiesto.", "remove-alias": "Rimuovi alias dispositivo", "add-alias": "Aggiungi alias dispositivo", "name-starts-with": "Dispositivo il cui nome inizia per", @@ -639,8 +661,8 @@ "accessTokenCopiedMessage": "Token di accesso del dispositivo copiato negli Appunti", "assignedToCustomer": "Assegnato al cliente", "unable-delete-device-alias-title": "Impossibile rimuovere l'alias del dispositivo", - "unable-delete-device-alias-text": "L'alias del dispositivo '{{deviceAlias}}' non può essere eliminato perchè utilizzato dai seguenti widget:
{{widgetsList}}", - "is-gateway": "E' un gateway", + "unable-delete-device-alias-text": "L'alias del dispositivo '{{deviceAlias}}' non può essere eliminato perché utilizzato dai seguenti widget:
{{widgetsList}}", + "is-gateway": "È un gateway", "public": "Pubblico", "device-public": "Il dispositivo è pubblico", "select-device": "Seleziona dispositivo" @@ -659,9 +681,9 @@ "aliases": "Alias entità", "entity-alias": "Alias entità", "unable-delete-entity-alias-title": "Impossibile eliminare alias entità", - "unable-delete-entity-alias-text": "L'alias dell'entità '{{entityAlias}}' non può essere eliminato perchè utilizzato dai seguenti widget:
{{widgetsList}}", - "duplicate-alias-error": "Trovato un duplicato dell'alias '{{alias}}'.
Gli alias dell'entità devono essere univoci all'interno della dashboard.", - "missing-entity-filter-error": "Filter is missing for alias '{{alias}}'.", + "unable-delete-entity-alias-text": "L'alias dell'entità '{{entityAlias}}' non può essere eliminato perché utilizzato dai seguenti widget:
{{widgetsList}}", + "duplicate-alias-error": "Trovato un duplicato dell'alias '{{alias}}'.
Gli alias dell'entità devono essere univoci all'interno della dashboard.", + "missing-entity-filter-error": "Manca il filtro per l'alias '{{alias}}'.", "configure-alias": "Configura '{{alias}}' alias", "alias": "Alias", "alias-required": "Alias entità obbligatorio.", @@ -701,6 +723,10 @@ "type-assets": "Asset", "list-of-assets": "{ count, plural, 1 {Un asset} other {Lista di # asset} }", "asset-name-starts-with": "Asset i cui nomi iniziano per '{{prefix}}'", + "type-entity-view": "Vista entità", + "type-entity-views": "Viste entità", + "list-of-entity-views": "{ count, plural, 1 {Una vista entità} other {Lista di # viste entità} }", + "entity-view-name-starts-with": "Viste entità i cui nomi iniziano per '{{prefix}}'", "type-rule": "Regola", "type-rules": "Regole", "list-of-rules": "{ count, plural, 1 {Una regola} other {Lista di # regole} }", @@ -711,7 +737,7 @@ "plugin-name-starts-with": "Plugin i cui nomi iniziano per '{{prefix}}'", "type-tenant": "Tenant", "type-tenants": "Tenants", - "list-of-tenants": "{ count, plural, 1 {One tenant} other {List of # tenants} }", + "list-of-tenants": "{ count, plural, 1 {One tenant} other {Lista di # tenants} }", "tenant-name-starts-with": "Tenants whose names start with '{{prefix}}'", "type-customer": "Cliente", "type-customers": "Clienti", @@ -719,7 +745,7 @@ "customer-name-starts-with": "Clienti i cui nomi iniziano per '{{prefix}}'", "type-user": "Utente", "type-users": "Utenti", - "list-of-users": "{ count, plural, 1 {Un utente} other {Lista of # utenti} }", + "list-of-users": "{ count, plural, 1 {Un utente} other {Lista di # utenti} }", "user-name-starts-with": "Utenti i cui nomi iniziano per '{{prefix}}'", "type-dashboard": "Dashboard", "type-dashboards": "Dashboard", @@ -730,16 +756,117 @@ "list-of-alarms": "{ count, plural, 1 {Un allarme} other {Lista di # allarmi} }", "alarm-name-starts-with": "Allarmi i cui nomi iniziano per '{{prefix}}'", "type-rulechain": "Rule chain", - "type-rulechains": "Rule chains", - "list-of-rulechains": "{ count, plural, 1 {One rule chain} other {List of # rule chains} }", - "rulechain-name-starts-with": "Rule chains whose names start with '{{prefix}}'", + "type-rulechains": "Rule chain", + "list-of-rulechains": "{ count, plural, 1 {Una rule chain} other {Lista di # catene di regole} }", + "rulechain-name-starts-with": "Catene di regole i cui nomi iniziano per '{{prefix}}'", + "type-rulenode": "Nodo regola", + "type-rulenodes": "Nodi regola", + "list-of-rulenodes": "{ count, plural, 1 {Un nodo regola} other {Lista di # nodi regola} }", + "rulenode-name-starts-with": "Nodi regola i cui nomi iniziano per '{{prefix}}'", "type-current-customer": "Cliente attuale", "search": "Ricerca entità", "selected-entities": "{ count, plural, 1 {1 entità selezionata} other {# entità selezionate} }", "entity-name": "Nome entità", "details": "Dettagli entità", "no-entities-prompt": "Nessuna entità trovata", - "no-data": "Nessun dato da mostrare" + "no-data": "Nessun dato da mostrare", + "columns-to-display": "Colonne da mostrare" + }, + "entity-view": { + "entity-view": "Vista entità", + "entity-view-required": "Vista entità richiesta.", + "entity-views": "Viste entità", + "management": "Gestione viste entità", + "view-entity-views": "Visualizza Viste entità", + "entity-view-alias": "Alias vista entità", + "aliases": "Alias vista entità", + "no-alias-matching": "'{{alias}}' non trovato.", + "no-aliases-found": "Nessun alias trovato.", + "no-key-matching": "'{{key}}' non trovata.", + "no-keys-found": "Nessuna chiave trovata.", + "create-new-alias": "Creane uno nuovo!", + "create-new-key": "Creane una nuova!", + "duplicate-alias-error": "Sono stati trovati dei duplicati dell'alias '{{alias}}'.
Gli alias di una vista entità devono essere univoci all'interno della dashboard.", + "configure-alias": "Configura alias '{{alias}}'", + "no-entity-views-matching": "Nessuna vista entità corrispondente a '{{entity}}' è stata trovato.", + "alias": "Alias", + "alias-required": "Alias vista entità richiesto.", + "remove-alias": "Rimuovi alias vista entità", + "add-alias": "Aggiungi alias vista entità", + "name-starts-with": "Vista entità il cui nome inizia per", + "entity-view-list": "Lista viste entità", + "use-entity-view-name-filter": "Usa filtro", + "entity-view-list-empty": "Nessuna vista entità selezionata.", + "entity-view-name-filter-required": "Filtro nome vista entità obbligatorio.", + "entity-view-name-filter-no-entity-view-matched": "Nessuna vista entità il cui nome inizia per '{{entity-view}}' è stata trovata.", + "add": "Aggiungi Vista entità", + "assign-to-customer": "Assegna al cliente", + "assign-entity-view-to-customer": "Assegna vista entità/viste entità al Cliente", + "assign-entity-view-to-customer-text": "Seleziona la vista entità da assegnare al cliente", + "no-entity-views-text": "Nessuna vista entità trovata", + "assign-to-customer-text": "Seleziona il cliente a cui assegnare la vista entità/le vista entità", + "entity-view-details": "Dettagli vista entità", + "add-entity-view-text": "Aggiungi nuova vista entità", + "delete": "Elimina vista entità", + "assign-entity-views": "Assegna viste entità", + "assign-entity-views-text": "Assegna { count, plural, 1 {1 vista entità} other {# viste entità} } al cliente", + "delete-entity-views": "Elimina viste entità", + "unassign-from-customer": "Annulla assegnazione al cliente", + "unassign-entity-views": "Annulla assegnazione viste entità", + "unassign-entity-views-action-title": "Annulla assegnazione { count, plural, 1 {1 vista entità} other {# viste entità} } al cliente", + "assign-new-entity-view": "Assegna nuova vista entità", + "delete-entity-view-title": "Sei sicuro di voler eliminare la vista entità '{{entity-viewName}}'?", + "delete-entity-view-text": "Attenzione, dopo la conferma la vista entità e tutti i suoi dati non saranno più recuperabili.", + "delete-entity-views-title": "Sei sicuro di voler eliminare { count, plural, 1 {1 vista entità} other {# viste entità} }?", + "delete-entity-views-action-title": "Elimina { count, plural, 1 {1 vista entità} other {# viste entità} }", + "delete-entity-views-text": "Attenzione, dopo la conferma tutte le vista entità selezionati saranno eliminate e i relativi dati non saranno più recuperabili.", + "unassign-entity-view-title": "Sei sicuro di voler annullare l'assegnazione della vista entità '{{entity-viewName}}'?", + "unassign-entity-view-text": "Dopo la conferma sarà annullata l'assegnazione della vista entità e questa non sarà più accessibile dal cliente.", + "unassign-entity-view": "Annulla assegnazione vista entità", + "unassign-entity-views-title": "Sei sicuro di voler annullare l'assegnazione di { count, plural, 1 {1 vista entità} other {# viste entità} }?", + "unassign-entity-views-text": "Dopo la conferma sarà annullata l'assegnazione di tutte le vista entità selezionate e queste non saranno più accessibili dal cliente.", + "entity-view-type": "Tipo vista entità", + "entity-view-type-required": "Tipo vista entità obbligatorio.", + "select-entity-view-type": "Seleziona tipo vista entità", + "enter-entity-view-type": "Inserisci tipo vista entità", + "any-entity-view": "Qualsiasi vista entità", + "no-entity-view-types-matching": "Nessuna vista entità corrispondente a '{{entitySubtype}}' è stata trovata.", + "entity-view-type-list-empty": "Nessun tipo di vista entità selezionato.", + "entity-view-types": "Tipi vista entità", + "name": "Nome", + "name-required": "Nome obbligatorio.", + "description": "Descrizione", + "events": "Eventi", + "details": "Dettagli", + "copyId": "Copia Id vista entità", + "assignedToCustomer": "Assegnata al cliente", + "unable-entity-view-device-alias-title": "Impossibile rimuovere l'alias del vista entità", + "unable-entity-view-device-alias-text": "L'alias del vista entità '{{entity-viewAlias}}' non può essere eliminato perché utilizzato dai seguenti widget:
{{widgetsList}}", + "select-entity-view": "Seleziona vista entità", + "make-public": "Rendi pubblica la vista entità", + "make-private": "Rendi privata la vista entità", + "start-date": "Data inizio", + "start-ts": "Ora inizio", + "end-date": "Data fine", + "end-ts": "Ora fine", + "date-limits": "Limiti temporali", + "client-attributes": "Attributi cliente", + "shared-attributes": "Attributi condivisi", + "server-attributes": "Attributi server", + "timeseries": "Serie temporali", + "client-attributes-placeholder": "Attributi cliente", + "shared-attributes-placeholder": "Attributi condivisi", + "server-attributes-placeholder": "Attributi server", + "timeseries-placeholder": "Serie temporali", + "target-entity": "Entità target", + "attributes-propagation": "Propagazione degli attributi", + "attributes-propagation-hint": "La vista entità copierà automaticamente gli attributi specificati dall'entità target ogni volta che questa vista entità sarà salvata e aggiornata. Per ragioni di performance, gli attributi dell'entità target non sono propagati alle viste entità ogni cambiamento di attributo. È possibile abilitare la propagazione automatica configurando il nodo regola \"Copia alla vista\" nella rule chain e collegando i messaggi \"Post attributes\" a \"Attributes Updated\" al nuovo nodo regola.", + "timeseries-data": "Dati delle serie temporali", + "timeseries-data-hint": "Imposta le chiavi delle serie temporali dell'entità target che saranno accessibili alla vista entità. Questi dati sono di sola lettura.", + "make-public-entity-view-title": "Sei sicuro di voler rendere pubblica la vista entità '{{entity-viewName}}'?", + "make-public-entity-view-text": "Dopo la conferma la vista entità e tutti i suoi dati saranno resi pubblici e accessibili dagli altri.", + "make-private-entity-view-title": "Sei sicuro di voler rendere privata la vista entità '{{entity-viewName}}'?", + "make-private-entity-view-text": "Dopo la conferma la vista entità e tutti i suoi dati saranno resi privati e non più accessibili da altri utenti." }, "event": { "event-type": "Tipo evento", @@ -795,8 +922,8 @@ "token": "Token di sicurezza", "add-converter": "Aggiungi convertitore", "add-config": "Aggiungi configurazione convertitore", - "device-name-expression": "Device name expression", - "device-type-expression": "Device type expression", + "device-name-expression": "Espressione nome dispositivo", + "device-type-expression": "Espressione tipo dispositivo", "custom": "Custom", "to-double": "To Double", "transformer": "Transformer", @@ -817,7 +944,7 @@ "credentials": "Credenziali", "username": "Nome utente", "password": "Password", - "retry-interval": "Retry interval in milliseconds", + "retry-interval": "Intervallo di ripetizione in millisecondi", "anonymous": "Anonimo", "basic": "Basic", "pem": "PEM", @@ -886,6 +1013,8 @@ "modbus-add-server": "Aggiungi server/slave", "modbus-add-server-prompt": "Aggiungi server/slave", "modbus-transport": "Transport", + "modbus-tcp-reconnect": "Riconnessione automatica", + "modbus-rtu-over-tcp": "RTU over TCP", "modbus-port-name": "Nome porta seriale", "modbus-encoding": "Codifica", "modbus-parity": "Parità", @@ -1077,9 +1206,9 @@ "name-required": "Nome obbligatorio.", "description": "Descrizione", "add": "Aggiungi Rule Chain", - "set-root": "Make rule chain root", - "set-root-rulechain-title": "Are you sure you want to make the rule chain '{{ruleChainName}}' root?", - "set-root-rulechain-text": "After the confirmation the rule chain will become root and will handle all incoming transport messages.", + "set-root": "Imposta la rule chain come root", + "set-root-rulechain-title": "Sei sicuro di voler impostare la rule chain '{{ruleChainName}}' come root?", + "set-root-rulechain-text": "Dopo la conferma la rule chain diverrà root a gestirà tutti i messaggi in arrivo.", "delete-rulechain-title": "Sei sicuro di voler eliminare la rule chain '{{ruleChainName}}'?", "delete-rulechain-text": "Attenzione, dopo la conferma la rule chain e tutti i dati relativi non saranno più recuperabili.", "delete-rulechains-title": "Sei sicuro di voler eliminare { count, plural, 1 {1 rule chain} other {# rule chain} }?", @@ -1110,33 +1239,38 @@ "events": "Eventi", "search": "Ricerca nodi", "open-node-library": "Apri libreria nodi", - "add": "Add rule node", + "add": "Aggiungi nodo regola", "name": "Nome", "name-required": "Nome obbligatorio.", "type": "Tipo", "description": "Descrizione", - "delete": "Delete rule node", + "delete": "Elimina nodo regola", "select-all-objects": "Seleziona tutti i nodi e le connessioni", - "deselect-all-objects": "Deselect all nodes and connections", + "deselect-all-objects": "Deseleziona tutti i nodi e le connessioni", "delete-selected-objects": "Cancella nodi e connessioni selezionate", - "delete-selected": "Delete selected", + "delete-selected": "Elimina selezionati", "select-all": "Seleziona tutto", "copy-selected": "Copia selezionata", "deselect-all": "Deseleziona tutto", - "rulenode-details": "Rule node details", + "rulenode-details": "Dettagli nodo regola", "debug-mode": "Modalità debug", "configuration": "Configurazione", "link": "Link", - "link-details": "Rule node link details", + "link-details": "Dettagli link nodo regola", "add-link": "Aggiungi link", "link-label": "Etichetta link", "link-label-required": "Etichetta link obbligatoria.", - "custom-link-label": "Custom link label", - "custom-link-label-required": "Custom link label is required.", + "custom-link-label": "Etichetta link personalizzata", + "custom-link-label-required": "Etichetta link personalizzata obbligatoria.", + "link-labels": "Etichette link", + "link-labels-required": "Etichette link richieste.", + "no-link-labels-found": "Nessuna etichetta link trovata.", + "no-link-label-matching": "'{{label}}' non trovata.", + "create-new-link-label": "Creane una nuova!", "type-filter": "Filtro", - "type-filter-details": "Filter incoming messages with configured conditions", + "type-filter-details": "Filtra i messaggi in arrivo con le condizioni configurate", "type-enrichment": "Enrichment", - "type-enrichment-details": "Add additional information into Message Metadata", + "type-enrichment-details": "Aggiungi informazioni addizionali nei metadati del messaggio", "type-transformation": "Transformation", "type-transformation-details": "Change Message payload and Metadata", "type-action": "Azioni", @@ -1147,12 +1281,15 @@ "type-rule-chain-details": "Forwards incoming messages to specified Rule Chain", "type-input": "Input", "type-input-details": "Logical input of Rule Chain, forwards incoming messages to next related Rule Node", + "type-unknown": "Sconosciuto", + "type-unknown-details": "Nodo regola non trovato", "directive-is-not-loaded": "Defined configuration directive '{{directiveName}}' is not available.", "ui-resources-load-error": "Failed to load configuration ui resources.", "invalid-target-rulechain": "Unable to resolve target rule chain!", "test-script-function": "Test script function", "message": "Messaggio", "message-type": "Tipo messaggio", + "select-message-type": "Seleziona tipo messaggio", "message-type-required": "Tipo messaggio obbligatorio", "metadata": "Metadata", "metadata-required": "Metadata entries can't be empty.", @@ -1200,13 +1337,13 @@ }, "timewindow": { "days": "{ days, plural, 1 { giorno } other {# giorni } }", - "hours": "{ hours, plural, 0 { hour } 1 {1 ora } other {# ore } }", - "minutes": "{ minutes, plural, 0 { minute } 1 {1 minuto } other {# minuti } }", - "seconds": "{ seconds, plural, 0 { second } 1 {1 secondo } other {# secondi } }", + "hours": "{ hours, plural, 0 { ora } 1 {1 ora } other {# ore } }", + "minutes": "{ minutes, plural, 0 { minuto } 1 {1 minuto } other {# minuti } }", + "seconds": "{ seconds, plural, 0 { secondo } 1 {1 secondo } other {# secondi } }", "realtime": "Realtime", "history": "Cronologia", "last-prefix": "ultimo", - "period": "from {{ startTime }} to {{ endTime }}", + "period": "da {{ startTime }} a {{ endTime }}", "edit": "Modifica intervallo temporale", "date-range": "Intervallo date", "last": "Ultimo", @@ -1215,7 +1352,7 @@ "user": { "user": "Utente", "users": "Utenti", - "customer-users": "Customer Users", + "customer-users": "Utente cliente", "tenant-admins": "Amministratori Tenant", "sys-admin": "Amministratore di sistema", "tenant-admin": "Amministratore tenant", @@ -1232,7 +1369,7 @@ "delete-users-action-title": "Elimina { count, plural, 1 {1 utente} other {# utenti} }", "delete-users-text": "Attenzione, dopo la conferma tutti gli utenti selezionati saranno eliminati e tutti i relativi dati non saranno più recuperabili.", "activation-email-sent-message": "Email di attivazione inviata con successo!", - "resend-activation": "Resend activation", + "resend-activation": "Invia di nuovo attivazione", "email": "Email", "email-required": "Email obbligatoria.", "invalid-email-format": "Formato email non valido.", @@ -1251,7 +1388,9 @@ "activation-link-text": "Per attivare l'utente utilizza il seguente link di attivazione :", "copy-activation-link": "Copia link di attivazione", "activation-link-copied-message": "Link di attivazione utente copiato negli appunti", - "details": "Dettagli" + "details": "Dettagli", + "login-as-tenant-admin": "Accedi come Amministratore tenant", + "login-as-customer-user": "Accedi come Utente cliente" }, "value": { "type": "Tipo valore", @@ -1274,7 +1413,7 @@ "select-widgets-bundle": "Seleziona bundle widget", "management": "Gestione widget", "editor": "Editor Widget", - "widget-type-not-found": "Problem loading widget configuration.
Probably associated\n widget type was removed.", + "widget-type-not-found": "Problem loading widget configuration.
Probably associated\n widget type was removed.", "widget-type-load-error": "Widget non caricato a causa dei seguenti errori:", "remove": "Elimina widget", "edit": "Modifica widget", @@ -1295,7 +1434,7 @@ "saveAs": "Salva widget come", "save-widget-type-as": "Salva tipo widget come", "save-widget-type-as-text": "Please enter new widget title and/or select target widgets bundle", - "toggle-fullscreen": "Toggle fullscreen", + "toggle-fullscreen": "Commuta modalità schermo intero", "run": "Esegui widget", "title": "Titolo widget", "title-required": "Titolo widget obbligatorio.", @@ -1308,7 +1447,7 @@ "tidy": "Tidy", "css": "CSS", "settings-schema": "Impostazioni schema", - "datakey-settings-schema": "Data key settings schema", + "datakey-settings-schema": "Impostazioni Data key schema", "javascript": "Javascript", "remove-widget-type-title": "Sei sicuro di voler rimuovere il tipo di widget '{{widgetName}}'?", "remove-widget-type-text": "Dopo la conferma il tipo di widget e tutti i suoi dati non saranno più recuperabili.", @@ -1367,7 +1506,7 @@ "general-settings": "Impostazioni generali", "display-title": "Mostra titolo", "drop-shadow": "Drop shadow", - "enable-fullscreen": "Abilita fullscreen", + "enable-fullscreen": "Abilita schermo intero", "background-color": "Colore sfondo", "text-color": "Colore testo", "padding": "Padding", @@ -1412,7 +1551,7 @@ "export": "Esporta un tipo di widget", "export-failed-error": "Impossibile esportare il tipo di widget: {{error}}", "create-new-widget-type": "Crea un nuovo tipo di widget", - "widget-type-file": "Widget type file", + "widget-type-file": "File tipo di widget", "invalid-widget-type-file-error": "Impossibile importare un tipo di widget: struttura dati del widget non valida." }, "icon": { @@ -1423,10 +1562,11 @@ }, "custom": { "widget-action": { - "action-cell-button": "Action cell button", - "row-click": "On row click", - "marker-click": "On marker click", - "tooltip-tag-action": "Tooltip tag action" + "action-cell-button": "Pulsante azione cella", + "row-click": "Click sulla riga", + "polygon-click": "Click sul poligono", + "marker-click": "Click sul marker", + "tooltip-tag-action": "Azione tooltip" } }, "language": { @@ -1435,9 +1575,9 @@ "de_DE": "Tedesco", "fr_FR": "Francese", "zh_CN": "Cinese", - "ko_KR": "Coreano", "en_US": "Inglese", "it_IT": "Italiano", + "ko_KR": "Coreano", "ru_RU": "Russo", "es_ES": "Spagnolo", "ja_JA": "Giapponese", @@ -1445,4 +1585,4 @@ "fa_IR": "Persiana" } } -} \ No newline at end of file +} diff --git a/ui/src/app/locale/locale.constant-ja_JA.json b/ui/src/app/locale/locale.constant-ja_JA.json index ce4d849216..9fe6971ff9 100644 --- a/ui/src/app/locale/locale.constant-ja_JA.json +++ b/ui/src/app/locale/locale.constant-ja_JA.json @@ -1442,6 +1442,7 @@ "widget-action": { "action-cell-button": "アクションセルボタン", "row-click": "行のクリック", + "polygon-click": "ポリゴンクリック", "marker-click": "マーカークリック", "tooltip-tag-action": "ツールチップのタグアクション" } diff --git a/ui/src/app/locale/locale.constant-ko_KR.json b/ui/src/app/locale/locale.constant-ko_KR.json index 57aef0e525..87f92dc3e4 100644 --- a/ui/src/app/locale/locale.constant-ko_KR.json +++ b/ui/src/app/locale/locale.constant-ko_KR.json @@ -1318,6 +1318,7 @@ "widget-action": { "action-cell-button": "Action cell button", "row-click": "On row click", + "polygon-click": "On polygon click", "marker-click": "On marker click", "tooltip-tag-action": "Tooltip tag action" } diff --git a/ui/src/app/locale/locale.constant-ru_RU.json b/ui/src/app/locale/locale.constant-ru_RU.json index d31d8666bc..3ad4371cc1 100644 --- a/ui/src/app/locale/locale.constant-ru_RU.json +++ b/ui/src/app/locale/locale.constant-ru_RU.json @@ -1558,6 +1558,7 @@ "action-cell-button": "Кнопка действия ячейки", "row-click": "Действий при щелчке на строку", "marker-click": "Действия при щелчке на указателе", + "polygon-click": "Действия при щелчке на полигон", "tooltip-tag-action": "Действие при подсказке" } }, diff --git a/ui/src/app/locale/locale.constant-tr_TR.json b/ui/src/app/locale/locale.constant-tr_TR.json index b96eaaf3ce..e65d07bd83 100644 --- a/ui/src/app/locale/locale.constant-tr_TR.json +++ b/ui/src/app/locale/locale.constant-tr_TR.json @@ -1524,7 +1524,8 @@ "widget-action": { "action-cell-button": "Eylem hücre butonu", "row-click": "Satır tıklama eylemi", - "marker-click": "İşaretçi tıklama eylemi", + "polygon-click": "Satır tıklama eylemi", + "marker-click": "Çokgen tıklama eylemi", "tooltip-tag-action": "İpucu etiket eylemi" } }, diff --git a/ui/src/app/locale/locale.constant-zh_CN.json b/ui/src/app/locale/locale.constant-zh_CN.json index 141c613065..eaf810c986 100644 --- a/ui/src/app/locale/locale.constant-zh_CN.json +++ b/ui/src/app/locale/locale.constant-zh_CN.json @@ -1429,6 +1429,7 @@ "action-cell-button": "动作单元格按钮", "row-click": "点击行", "marker-click": "点击标记", + "polygon-click": "单击多边形", "tooltip-tag-action": "提示标签动作" } }, diff --git a/ui/src/app/widget/lib/google-map.js b/ui/src/app/widget/lib/google-map.js index 157e656aac..ba1ae1f65e 100644 --- a/ui/src/app/widget/lib/google-map.js +++ b/ui/src/app/widget/lib/google-map.js @@ -315,7 +315,7 @@ export default class TbGoogleMap { } - createPolygon(latLangs, settings) { + createPolygon(latLangs, settings, location, onClickListener, markerArgs) { let polygon = new google.maps.Polygon({ map: this.map, paths: latLangs, @@ -325,6 +325,32 @@ export default class TbGoogleMap { fillOpacity: settings.polygonOpacity, strokeWeight: settings.polygonStrokeWeight }); + + //initialize-tooltip + + let popup = new google.maps.InfoWindow({ + content: '' + }); + if (!this.tooltips) this.tooltips = []; + this.tooltips.push({ + markerArgs: markerArgs, + popup: popup, + locationSettings: settings, + dsIndex: location.dsIndex + }); + + if (onClickListener) { + google.maps.event.addListener(polygon, 'click', function (event) { + if (settings.displayTooltip) { + if (!polygon.anchor) { + polygon.anchor = new google.maps.MVCObject(); + } + polygon.anchor.set("position", event.latLng); + popup.open(this.map, polygon.anchor); + } + onClickListener(); + }); + } return polygon; } /* eslint-disable no-undef */ diff --git a/ui/src/app/widget/lib/map-widget2.js b/ui/src/app/widget/lib/map-widget2.js index 85e94b7183..42e9cdb463 100644 --- a/ui/src/app/widget/lib/map-widget2.js +++ b/ui/src/app/widget/lib/map-widget2.js @@ -233,7 +233,6 @@ export default class TbMapWidgetV2 { } update() { - var tbMap = this; @@ -381,6 +380,17 @@ export default class TbMapWidgetV2 { tbMap.ctx.actionsApi.handleWidgetAction($event, descriptors[0], entityId, entityName); } } + function locationPolygonClick($event, location) { + var descriptors = tbMap.ctx.actionsApi.getActionDescriptors('polygonClick'); + if (descriptors.length) { + var datasource = tbMap.subscription.datasources[location.dsIndex]; + var entityId = {}; + entityId.id = datasource.entityId; + entityId.entityType = datasource.entityType; + var entityName = datasource.entityName; + tbMap.ctx.actionsApi.handleWidgetAction($event, descriptors[0], entityId, entityName); + } + } function updateLocation(location, data, dataMap) { var locationChanged = false; @@ -427,21 +437,6 @@ export default class TbMapWidgetV2 { locationChanged = true; } } - - - if (location.settings.showPolygon && dataMap.dsDataMap[location.dsIndex][location.settings.polygonKeyName] !== null) { - let polygonLatLngsRaw = angular.fromJson(dataMap.dsDataMap[location.dsIndex][location.settings.polygonKeyName]); - let polygonLatLngs = !polygonLatLngsRaw || mapPolygonArray(polygonLatLngsRaw); - if (!location.polygon && polygonLatLngs.length > 0) { - location.polygon = tbMap.map.createPolygon(polygonLatLngs, location.settings); - tbMap.polygons.push(location.polygon); - } else if (polygonLatLngs.length > 0) { - let prevPolygonArr = tbMap.map.getPolygonLatLngs(location.polygon); - if (!prevPolygonArr || !arraysEqual(prevPolygonArr, polygonLatLngs)) { - tbMap.map.setPolygonLatLngs(location.polygon, polygonLatLngs); - } - } - } } if (location.marker) { updateLocationStyle(location, dataMap); @@ -451,6 +446,25 @@ export default class TbMapWidgetV2 { return locationChanged; } + function createUpdatePolygon(location, dataMap) { + if (location.settings.showPolygon && dataMap.dsDataMap[location.dsIndex][location.settings.polygonKeyName] !== null) { + let polygonLatLngsRaw = angular.fromJson(dataMap.dsDataMap[location.dsIndex][location.settings.polygonKeyName]); + let polygonLatLngs = !polygonLatLngsRaw || mapPolygonArray(polygonLatLngsRaw); + if (!location.polygon && polygonLatLngs.length > 0) { + location.polygon = tbMap.map.createPolygon(polygonLatLngs, location.settings, location, function (event) { + tbMap.callbacks.onLocationClick(location); + locationPolygonClick(event, location); + }, [location.dsIndex]); + tbMap.polygons.push(location.polygon); + } else if (polygonLatLngs.length > 0) { + let prevPolygonArr = tbMap.map.getPolygonLatLngs(location.polygon); + if (!prevPolygonArr || !arraysEqual(prevPolygonArr, polygonLatLngs)) { + tbMap.map.setPolygonLatLngs(location.polygon, polygonLatLngs); + } + } + } + } + function loadLocations(data, datasources) { var bounds = tbMap.map.createBounds(); tbMap.locations = []; @@ -459,7 +473,6 @@ export default class TbMapWidgetV2 { var currentDatasourceIndex = -1; var latIndex = -1; var lngIndex = -1; - for (var i = 0; i < data.length; i++) { var dataKeyData = data[i]; var dataKey = dataKeyData.dataKey; @@ -480,7 +493,6 @@ export default class TbMapWidgetV2 { } else if (nameToCheck === tbMap.locationSettings.lngKeyName) { lngIndex = i; } - if (latIndex > -1 && lngIndex > -1) { var location = { latIndex: latIndex, @@ -499,6 +511,7 @@ export default class TbMapWidgetV2 { } tbMap.locations.push(location); updateLocation(location, data, dataMap); + if (location.polyline) { tbMap.map.extendBounds(bounds, location.polyline); } else if (location.marker) { @@ -507,8 +520,28 @@ export default class TbMapWidgetV2 { latIndex = -1; lngIndex = -1; } - } + data.forEach(function (dataEl, index) { + let nameToCheck; + if (dataEl.dataKey.locationAttrName) { + nameToCheck = dataEl.dataKey.locationAttrName; + } else { + nameToCheck = dataEl.dataKey.label; + } + if (nameToCheck === tbMap.locationSettings.polygonKeyName) { + let location = { + polIndex: index, + settings: angular.copy(tbMap.locationSettings) + }; + location.dsIndex = datasources.findIndex(function (ds) { + return dataEl.datasource.entityId === ds.entityId; + }); + tbMap.locations.push(location); + createUpdatePolygon(location, dataMap); + tbMap.map.extendBounds(bounds, location.polygon); + } + }); + tbMap.map.fitBounds(bounds); } @@ -532,10 +565,13 @@ export default class TbMapWidgetV2 { for (var p = 0; p < tbMap.locations.length; p++) { var location = tbMap.locations[p]; locationsChanged |= updateLocation(location, data, dataMap); + createUpdatePolygon(location, dataMap); if (location.polyline) { tbMap.map.extendBounds(bounds, location.polyline); } else if (location.marker) { tbMap.map.extendBoundsWithMarker(bounds, location.marker); + } else if (location.polygon) { + tbMap.map.extendBounds(bounds, location.polygon); } } if (locationsChanged && tbMap.initBounds) { @@ -585,23 +621,30 @@ export default class TbMapWidgetV2 { } } } - } resize() { if (this.map && this.map.inited()) { - this.map.invalidateSize(); + let map = this.map; if (this.locations && this.locations.length > 0) { - var bounds = this.map.createBounds(); - for (var m = 0; m < this.markers.length; m++) { - this.map.extendBoundsWithMarker(bounds, this.markers[m]); + map.invalidateSize(); + var bounds = map.createBounds(); + if (this.markers && this.markers.length>0) { + this.markers.forEach(function (marker) { + map.extendBoundsWithMarker(bounds, marker); + }); } - if (this.polylines) { - for (var p = 0; p < this.polylines.length; p++) { - this.map.extendBounds(bounds, this.polylines[p]); - } + if (this.polylines && this.polylines.length>0) { + this.polylines.forEach(function (polyline) { + map.extendBounds(bounds, polyline); + }) + } + if (this.polygons && this.polygons.length > 0) { + this.polygons.forEach(function (polygon) { + map.extendBounds(bounds, polygon); + }) } - this.map.fitBounds(bounds); + map.fitBounds(bounds); } } } @@ -638,6 +681,10 @@ export default class TbMapWidgetV2 { name: 'widget-action.marker-click', multiple: false }, + 'polygonClick': { + name: 'widget-action.polygon-click', + multiple: false + }, 'tooltipAction': { name: 'widget-action.tooltip-tag-action', multiple: true diff --git a/ui/src/app/widget/lib/openstreet-map.js b/ui/src/app/widget/lib/openstreet-map.js index eea7e86490..33f9ae09e2 100644 --- a/ui/src/app/widget/lib/openstreet-map.js +++ b/ui/src/app/widget/lib/openstreet-map.js @@ -190,7 +190,7 @@ export default class TbOpenStreetMap { this.map.removeLayer(polyline); } - createPolygon(latLangs, settings) { + createPolygon(latLangs, settings, location, onClickListener, markerArgs) { let polygon = L.polygon(latLangs, { fill: true, fillColor: settings.polygonColor, @@ -199,6 +199,13 @@ export default class TbOpenStreetMap { fillOpacity: settings.polygonOpacity, opacity: settings.polygonStrokeOpacity }).addTo(this.map); + + if (settings.displayTooltip) { + this.createTooltip(polygon, location.dsIndex, settings, markerArgs); + } + if (onClickListener) { + polygon.on('click', onClickListener); + } return polygon; } @@ -224,6 +231,7 @@ export default class TbOpenStreetMap { setPolygonLatLngs(polygon, latLngs) { polygon.setLatLngs(latLngs); + polygon.redraw(); } fitBounds(bounds) { @@ -272,7 +280,7 @@ export default class TbOpenStreetMap { } extendBounds(bounds, polyline) { - if (polyline && polyline.getLatLngs()) { + if (polyline && polyline.getLatLngs() && polyline.getBounds()) { bounds.extend(polyline.getBounds()); } } diff --git a/ui/src/app/widget/lib/tencent-map.js b/ui/src/app/widget/lib/tencent-map.js index 28f9e98268..6ce387e46a 100644 --- a/ui/src/app/widget/lib/tencent-map.js +++ b/ui/src/app/widget/lib/tencent-map.js @@ -331,7 +331,7 @@ export default class TbTencentMap { } /* eslint-disable no-undef */ - createPolygon(latLangs, settings) { + createPolygon(latLangs, settings, location, onClickListener, markerArgs) { let polygon = new qq.maps.Polygon({ map: this.map, path: latLangs, @@ -339,8 +339,31 @@ export default class TbTencentMap { fillColor: settings.polygonColor, strokeWeight: settings.polygonStrokeWeight }); + //initialize-tooltip + let popup = new qq.maps.InfoWindow({ + content: '' + }); + if (!this.tooltips) this.tooltips = []; + this.tooltips.push({ + markerArgs: markerArgs, + popup: popup, + locationSettings: settings, + dsIndex: location.dsIndex + }); + + if (onClickListener) { + qq.maps.event.addListener(polygon, 'click', function (event) { + if (settings.displayTooltip) { + popup.setMap(this.map); + popup.setPosition(event.latLng); + popup.open(); + } + onClickListener(); + }); + } return polygon; } + /* eslint-disable no-undef */ removePolygon (polygon) { diff --git a/ui/src/app/widget/lib/widget-utils.js b/ui/src/app/widget/lib/widget-utils.js index 190660c1b1..36bc6565e8 100644 --- a/ui/src/app/widget/lib/widget-utils.js +++ b/ui/src/app/widget/lib/widget-utils.js @@ -73,22 +73,24 @@ export function processPattern(pattern, datasources, dsIndex) { export function fillPattern(pattern, replaceInfo, data) { var text = angular.copy(pattern); - for (var v = 0; v < replaceInfo.variables.length; v++) { - var variableInfo = replaceInfo.variables[v]; - var txtVal = ''; - if (variableInfo.dataKeyIndex > -1 && data[variableInfo.dataKeyIndex]) { - var varData = data[variableInfo.dataKeyIndex].data; - if (varData.length > 0) { - var val = varData[varData.length - 1][1]; - if (isNumber(val)) { - txtVal = padValue(val, variableInfo.valDec, 0); - } else { - txtVal = val; - } - } - } - text = text.split(variableInfo.variable).join(txtVal); - } + if (replaceInfo) { + for (var v = 0; v < replaceInfo.variables.length; v++) { + var variableInfo = replaceInfo.variables[v]; + var txtVal = ''; + if (variableInfo.dataKeyIndex > -1 && data[variableInfo.dataKeyIndex]) { + var varData = data[variableInfo.dataKeyIndex].data; + if (varData.length > 0) { + var val = varData[varData.length - 1][1]; + if (isNumber(val)) { + txtVal = padValue(val, variableInfo.valDec, 0); + } else { + txtVal = val; + } + } + } + text = text.split(variableInfo.variable).join(txtVal); + } + } return text; } diff --git a/ui/webpack.config.prod.js b/ui/webpack.config.prod.js index f7093e7273..446b48a848 100644 --- a/ui/webpack.config.prod.js +++ b/ui/webpack.config.prod.js @@ -96,7 +96,7 @@ module.exports = { new CompressionPlugin({ asset: "[path].gz[query]", algorithm: "gzip", - test: /\.js$|\.css$|\.svg$|\.ttf$|\.woff$|\.woff2|\.eot$\.json$/, + test: /\.js$|\.css$|\.svg$|\.ttf$|\.woff$|\.woff2$|\.eot$|\.json$/, threshold: 10240, minRatio: 0.8 }),