diff --git a/application/src/main/java/org/thingsboard/server/service/iot_hub/DefaultIotHubService.java b/application/src/main/java/org/thingsboard/server/service/iot_hub/DefaultIotHubService.java index 2654fd7f59..b4a50e9086 100644 --- a/application/src/main/java/org/thingsboard/server/service/iot_hub/DefaultIotHubService.java +++ b/application/src/main/java/org/thingsboard/server/service/iot_hub/DefaultIotHubService.java @@ -24,6 +24,8 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.cf.CalculatedField; +import org.thingsboard.server.common.data.cf.CalculatedFieldType; +import org.thingsboard.server.common.data.id.CalculatedFieldId; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DeviceId; @@ -32,6 +34,7 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.IotHubInstalledItemId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.iot_hub.AlarmRuleInstalledItemDescriptor; import org.thingsboard.server.common.data.iot_hub.CalculatedFieldInstalledItemDescriptor; import org.thingsboard.server.common.data.iot_hub.DashboardInstalledItemDescriptor; import org.thingsboard.server.common.data.iot_hub.DeviceInstalledItemDescriptor; @@ -114,8 +117,7 @@ public class DefaultIotHubService implements IotHubService { case "WIDGET" -> installWidget(user, tenantId, fileData); case "DASHBOARD" -> installDashboard(user, tenantId, fileData); case "CALCULATED_FIELD" -> installCalculatedField(user, tenantId, fileData, data); - case "ALARM_RULE" -> throw new IllegalArgumentException( - "Alarm Rules require ThingsBoard 4.3 or later. Please update your platform instance to install Alarm Rule packages."); + case "ALARM_RULE" -> installAlarmRule(user, tenantId, fileData, data); case "RULE_CHAIN" -> installRuleChain(tenantId, fileData); case "DEVICE" -> installDeviceProfile(user, tenantId, fileData); case "SOLUTION_TEMPLATE" -> installSolution(user, tenantId, fileData, request); @@ -198,6 +200,30 @@ public class DefaultIotHubService implements IotHubService { return descriptor; } + private AlarmRuleInstalledItemDescriptor installAlarmRule(SecurityUser user, TenantId tenantId, byte[] fileData, JsonNode data) throws Exception { + CalculatedField alarmRule; + try { + alarmRule = JacksonUtil.fromString(new String(fileData), CalculatedField.class, true); + } catch (Exception e) { + throw new Exception("Failed to parse alarm rule data: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()), e); + } + if (alarmRule.getType() != CalculatedFieldType.ALARM) { + throw new Exception("Expected ALARM-type calculated field for ALARM_RULE install; got: " + alarmRule.getType()); + } + alarmRule.setId(null); + alarmRule.setTenantId(tenantId); + if (data != null && data.has("entityId")) { + EntityId entityId = JacksonUtil.treeToValue(data.get("entityId"), EntityId.class); + alarmRule.setEntityId(entityId); + } + CalculatedField saved = tbCalculatedFieldService.save(alarmRule, user); + log.debug("[{}] Alarm rule installed: {}", tenantId, saved.getName()); + AlarmRuleInstalledItemDescriptor descriptor = new AlarmRuleInstalledItemDescriptor(); + descriptor.setCalculatedFieldId(saved.getId()); + descriptor.setEntityId(saved.getEntityId()); + return descriptor; + } + private RuleChainInstalledItemDescriptor installRuleChain(TenantId tenantId, byte[] fileData) throws Exception { JsonNode json = JacksonUtil.toJsonNode(new String(fileData)); @@ -301,6 +327,7 @@ public class DefaultIotHubService implements IotHubService { case "WIDGET" -> updateWidget(user, tenantId, (WidgetInstalledItemDescriptor) descriptor, fileData); case "DASHBOARD" -> updateDashboard(user, tenantId, (DashboardInstalledItemDescriptor) descriptor, fileData); case "CALCULATED_FIELD" -> updateCalculatedField(user, tenantId, (CalculatedFieldInstalledItemDescriptor) descriptor, fileData); + case "ALARM_RULE" -> updateAlarmRule(user, tenantId, (AlarmRuleInstalledItemDescriptor) descriptor, fileData); case "RULE_CHAIN" -> updateRuleChain(tenantId, (RuleChainInstalledItemDescriptor) descriptor, fileData); case "DEVICE" -> { DeviceInstalledItemDescriptor dd = (DeviceInstalledItemDescriptor) descriptor; @@ -387,6 +414,26 @@ public class DefaultIotHubService implements IotHubService { tbCalculatedFieldService.save(existing, user); } + private void updateAlarmRule(SecurityUser user, TenantId tenantId, AlarmRuleInstalledItemDescriptor descriptor, byte[] fileData) throws Exception { + CalculatedField newCf; + try { + newCf = JacksonUtil.fromString(new String(fileData), CalculatedField.class, true); + } catch (Exception e) { + throw new Exception("Failed to parse alarm rule data: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()), e); + } + if (newCf.getType() != CalculatedFieldType.ALARM) { + throw new Exception("Expected ALARM-type calculated field for ALARM_RULE update; got: " + newCf.getType()); + } + CalculatedField existing = calculatedFieldService.findById(tenantId, descriptor.getCalculatedFieldId()); + if (existing == null) { + throw new Exception("Alarm rule not found for update"); + } + existing.setName(newCf.getName()); + existing.setType(newCf.getType()); + existing.setConfiguration(newCf.getConfiguration()); + tbCalculatedFieldService.save(existing, user); + } + private void updateRuleChain(TenantId tenantId, RuleChainInstalledItemDescriptor descriptor, byte[] fileData) throws Exception { JsonNode json = JacksonUtil.toJsonNode(new String(fileData)); RuleChainMetaData metadata; @@ -423,15 +470,17 @@ public class DefaultIotHubService implements IotHubService { } else if (descriptor instanceof DashboardInstalledItemDescriptor dd) { return calculateDashboardChecksum(tenantId, dd); } else if (descriptor instanceof CalculatedFieldInstalledItemDescriptor cd) { - return calculateCalculatedFieldChecksum(tenantId, cd); + return calculateCalculatedFieldChecksum(tenantId, cd.getCalculatedFieldId()); + } else if (descriptor instanceof AlarmRuleInstalledItemDescriptor ad) { + return calculateCalculatedFieldChecksum(tenantId, ad.getCalculatedFieldId()); } else if (descriptor instanceof RuleChainInstalledItemDescriptor rd) { return calculateRuleChainChecksum(tenantId, rd); } return null; } - private String calculateCalculatedFieldChecksum(TenantId tenantId, CalculatedFieldInstalledItemDescriptor descriptor) { - CalculatedField cf = calculatedFieldService.findById(tenantId, descriptor.getCalculatedFieldId()); + private String calculateCalculatedFieldChecksum(TenantId tenantId, CalculatedFieldId calculatedFieldId) { + CalculatedField cf = calculatedFieldService.findById(tenantId, calculatedFieldId); if (cf == null) { return null; } @@ -590,6 +639,11 @@ public class DefaultIotHubService implements IotHubService { if (calculatedField != null) { tbCalculatedFieldService.delete(calculatedField, user); } + } else if (descriptor instanceof AlarmRuleInstalledItemDescriptor ad) { + CalculatedField alarmRule = calculatedFieldService.findById(tenantId, ad.getCalculatedFieldId()); + if (alarmRule != null) { + tbCalculatedFieldService.delete(alarmRule, user); + } } else if (descriptor instanceof RuleChainInstalledItemDescriptor rd) { if (rd.getRuleChainId() != null) { RuleChain ruleChain = ruleChainService.findRuleChainById(tenantId, rd.getRuleChainId()); diff --git a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-actions.service.ts b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-actions.service.ts index 4fd1565c07..ee32245eb1 100644 --- a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-actions.service.ts +++ b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-actions.service.ts @@ -18,8 +18,6 @@ import { Injectable } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { Observable, of, EMPTY } from 'rxjs'; import { filter, mergeMap } from 'rxjs/operators'; -import { TranslateService } from '@ngx-translate/core'; -import { DialogService } from '@core/services/dialog.service'; import { MpItemVersionView } from '@shared/models/iot-hub/iot-hub-version.models'; import { ItemType } from '@shared/models/iot-hub/iot-hub-item.models'; import { DeviceInstalledItemDescriptor, IotHubInstalledItem } from '@shared/models/iot-hub/iot-hub-installed-item.models'; @@ -38,9 +36,7 @@ export class IotHubActionsService { constructor( private dialog: MatDialog, - private iotHubApiService: IotHubApiService, - private dialogService: DialogService, - private translate: TranslateService + private iotHubApiService: IotHubApiService ) {} openItemDetail(item: MpItemVersionView, installedItem?: IotHubInstalledItem, installedItemsCount?: number, @@ -76,13 +72,6 @@ export class IotHubActionsService { } installItem(item: MpItemVersionView): Observable { - if (item.type === ItemType.ALARM_RULE) { - this.dialogService.alert( - this.translate.instant('iot-hub.alarm-rule-install-update-required'), - this.translate.instant('iot-hub.alarm-rule-install-update-required-text') - ); - return EMPTY; - } if (item.type === ItemType.DEVICE) { return this.installDevice(item); } diff --git a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-install-dialog.component.ts b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-install-dialog.component.ts index 44e4d1a601..4b56427f06 100644 --- a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-install-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-install-dialog.component.ts @@ -75,7 +75,7 @@ export class TbIotHubInstallDialogComponent extends DialogComponent