Browse Source

LTS 4.3 IoT hub alarm rules (#15548)

* Feature/iot hub alarm rules (#15539)

* feat(iot-hub): scaffold ALARM_RULE item type and CREATOR_VISIBLE_ITEM_TYPES

* feat(iot-hub): wire ALARM_RULE through browse, item card, detail dialog, installed items, and install dialog

* feat(iot-hub): reorder home cards, hide Dashboards, add Alarm Rules

* feat(iot-hub): scaffold ALARM_RULE descriptor and reject install with v4.3 upgrade hint

Registers AlarmRuleInstalledItemDescriptor in the Jackson polymorphism so
4.2 can browse Alarm Rule items from the marketplace without descriptor
deserialization failures.

The install handler explicitly rejects ALARM_RULE with a friendly message
asking the user to upgrade to v4.3+. The full install/update/delete
implementation depends on CalculatedFieldType.ALARM (added in v4.3) and
will land once master is merged into this branch.

* feat(iot-hub): hide redundant Type filter on Alarm Rules browse

* feat(iot-hub): show v4.3 update info dialog directly when installing an Alarm Rule

* feat(iot-hub): implement ALARM_RULE install / update / delete handlers

Replaces the temporary v4.3-upgrade-required rejection with the actual
backend handlers. Alarm rules install as CalculatedField entities with
type=ALARM, mirroring the CALCULATED_FIELD path:

- installAlarmRule parses the alarm-rule JSON, validates type==ALARM,
  saves via tbCalculatedFieldService, and returns
  AlarmRuleInstalledItemDescriptor with calculatedFieldId + entityId.
- updateAlarmRule re-saves the existing CF with new name, type, and
  configuration.
- Delete branch removes the underlying calculated field on installed-item
  delete.
- Checksum dispatch unifies CF and ALARM_RULE through a shared
  calculateCalculatedFieldChecksum(CalculatedFieldId) helper.

Frontend: drops the v4.3 info-dialog interception in IotHubActionsService
(install now actually runs); restores the select-entity branch for
ALARM_RULE in the install dialog so users can pick a parent device.
Removes the now-unused alarm-rule-install-update-required* locale keys.
pull/15646/head
Andrew Shvayka 3 months ago
committed by GitHub
parent
commit
9212c0240a
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 62
      application/src/main/java/org/thingsboard/server/service/iot_hub/DefaultIotHubService.java
  2. 28
      common/data/src/main/java/org/thingsboard/server/common/data/iot_hub/AlarmRuleInstalledItemDescriptor.java
  3. 1
      common/data/src/main/java/org/thingsboard/server/common/data/iot_hub/IotHubInstalledItemDescriptor.java
  4. 2
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.html
  5. 31
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.ts
  6. 2
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-components.models.ts
  7. 2
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-install-dialog.component.ts
  8. 4
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-installed-items-table.component.ts
  9. 9
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-card.component.ts
  10. 13
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-detail-dialog.component.ts
  11. 1
      ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-deep-link.utils.ts
  12. 56
      ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.html
  13. 4
      ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.scss
  14. 70
      ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.ts
  15. 7
      ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-items-page.component.ts
  16. 10
      ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-routing.module.ts
  17. 7
      ui-ngx/src/app/shared/models/iot-hub/iot-hub-installed-item.models.ts
  18. 17
      ui-ngx/src/app/shared/models/iot-hub/iot-hub-item.models.ts
  19. 7
      ui-ngx/src/assets/locale/locale.constant-en_US.json

62
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,6 +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" -> 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);
@ -196,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));
@ -299,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;
@ -385,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;
@ -421,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;
}
@ -588,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());

28
common/data/src/main/java/org/thingsboard/server/common/data/iot_hub/AlarmRuleInstalledItemDescriptor.java

@ -0,0 +1,28 @@
/**
* Copyright © 2016-2026 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.common.data.iot_hub;
import lombok.Data;
import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.data.id.EntityId;
@Data
public class AlarmRuleInstalledItemDescriptor implements IotHubInstalledItemDescriptor {
private CalculatedFieldId calculatedFieldId;
private EntityId entityId;
}

1
common/data/src/main/java/org/thingsboard/server/common/data/iot_hub/IotHubInstalledItemDescriptor.java

@ -30,6 +30,7 @@ import java.io.Serializable;
@Type(name = "WIDGET", value = WidgetInstalledItemDescriptor.class),
@Type(name = "DASHBOARD", value = DashboardInstalledItemDescriptor.class),
@Type(name = "CALCULATED_FIELD", value = CalculatedFieldInstalledItemDescriptor.class),
@Type(name = "ALARM_RULE", value = AlarmRuleInstalledItemDescriptor.class),
@Type(name = "RULE_CHAIN", value = RuleChainInstalledItemDescriptor.class),
@Type(name = "DEVICE", value = DeviceInstalledItemDescriptor.class),
@Type(name = "SOLUTION_TEMPLATE", value = SolutionTemplateInstalledItemDescriptor.class)

2
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.html

@ -36,7 +36,7 @@
<!-- Filter panel template (reused in sidebar and drawer) -->
<ng-template #filterPanelContent>
<div class="tb-iot-hub-filters-inner">
@if (!fixedSubType && subtypeOptions.length > 0) {
@if (!fixedSubType && subtypeOptions.length > 0 && activeType !== ItemType.ALARM_RULE) {
<mat-expansion-panel [expanded]="true" class="tb-iot-hub-filter-panel">
<mat-expansion-panel-header>
<mat-panel-title>{{ 'iot-hub.type-filter-label' | translate }}</mat-panel-title>

31
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.ts

@ -21,7 +21,7 @@ import { PageLink } from '@shared/models/page/page-link';
import { Direction, SortOrder } from '@shared/models/page/sort-order';
import { PageData } from '@shared/models/page/page-data';
import { MpItemVersionQuery, MpItemVersionView } from '@shared/models/iot-hub/iot-hub-version.models';
import { ItemType, FilterParamInfo } from '@shared/models/iot-hub/iot-hub-item.models';
import { ItemType, FilterParamInfo, CREATOR_VISIBLE_ITEM_TYPES } from '@shared/models/iot-hub/iot-hub-item.models';
import { widgetTypeTranslations, cfTypeTranslations, ruleChainTypeTranslations } from '@shared/models/iot-hub/iot-hub-version.models';
import { IotHubInstalledItem, DeviceInstalledItemDescriptor } from '@shared/models/iot-hub/iot-hub-installed-item.models';
import { IotHubApiService } from '@core/http/iot-hub-api.service';
@ -45,6 +45,18 @@ interface SortOption {
})
export class TbIotHubBrowseComponent implements OnInit, AfterViewInit, OnDestroy {
private static typeTabLabel(t: ItemType): string {
switch (t) {
case ItemType.WIDGET: return 'item.type-widget-plural';
case ItemType.DASHBOARD: return 'item.type-dashboard-plural';
case ItemType.SOLUTION_TEMPLATE: return 'item.type-solution-template-plural';
case ItemType.CALCULATED_FIELD: return 'item.type-calculated-field-plural';
case ItemType.ALARM_RULE: return 'item.type-alarm-rule-plural';
case ItemType.RULE_CHAIN: return 'item.type-rule-chain-plural';
case ItemType.DEVICE: return 'item.type-device-plural';
}
}
readonly ItemType = ItemType;
@Input() creatorId: string;
@ -76,7 +88,9 @@ export class TbIotHubBrowseComponent implements OnInit, AfterViewInit, OnDestroy
}
get isCompactType(): boolean {
return this._activeType === ItemType.CALCULATED_FIELD || this._activeType === ItemType.RULE_CHAIN;
return this._activeType === ItemType.CALCULATED_FIELD
|| this._activeType === ItemType.ALARM_RULE
|| this._activeType === ItemType.RULE_CHAIN;
}
items: MpItemVersionView[] = [];
@ -118,14 +132,10 @@ export class TbIotHubBrowseComponent implements OnInit, AfterViewInit, OnDestroy
{ value: 'name', label: 'iot-hub.sort-name', direction: Direction.ASC }
];
typeTabs: { type: ItemType; label: string }[] = [
{ type: ItemType.WIDGET, label: 'item.type-widget-plural' },
{ type: ItemType.DASHBOARD, label: 'item.type-dashboard-plural' },
{ type: ItemType.SOLUTION_TEMPLATE, label: 'item.type-solution-template-plural' },
{ type: ItemType.CALCULATED_FIELD, label: 'item.type-calculated-field-plural' },
{ type: ItemType.RULE_CHAIN, label: 'item.type-rule-chain-plural' },
{ type: ItemType.DEVICE, label: 'item.type-device-plural' }
];
typeTabs: { type: ItemType; label: string }[] = CREATOR_VISIBLE_ITEM_TYPES.map(t => ({
type: t,
label: TbIotHubBrowseComponent.typeTabLabel(t)
}));
selectedSortIndex = 0;
subtypeOptions: FilterParamInfo[] = [];
@ -512,6 +522,7 @@ export class TbIotHubBrowseComponent implements OnInit, AfterViewInit, OnDestroy
case ItemType.DASHBOARD: return 'iot-hub.title-dashboards';
case ItemType.SOLUTION_TEMPLATE: return 'iot-hub.title-solution-templates';
case ItemType.CALCULATED_FIELD: return 'iot-hub.title-calculated-fields';
case ItemType.ALARM_RULE: return 'iot-hub.title-alarm-rules';
case ItemType.RULE_CHAIN: return 'iot-hub.title-rule-chains';
case ItemType.DEVICE: return 'iot-hub.title-devices';
}

2
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-components.models.ts

@ -22,6 +22,7 @@ export const ITEM_TYPE_TO_ENTITY_TYPE: Record<string, EntityType> = {
'WIDGET': EntityType.WIDGET_TYPE,
'DASHBOARD': EntityType.DASHBOARD,
'CALCULATED_FIELD': EntityType.CALCULATED_FIELD,
'ALARM_RULE': EntityType.CALCULATED_FIELD,
'RULE_CHAIN': EntityType.RULE_CHAIN,
'DEVICE': EntityType.DEVICE_PROFILE
};
@ -39,6 +40,7 @@ export function resolveEntityDetailsUrl(descriptor: IotHubInstalledItemDescripto
case 'WIDGET': entityId = descriptor.widgetTypeId?.id; break;
case 'DASHBOARD': entityId = descriptor.dashboardId?.id; break;
case 'CALCULATED_FIELD': entityId = descriptor.calculatedFieldId?.id; break;
case 'ALARM_RULE': entityId = descriptor.calculatedFieldId?.id; break;
case 'RULE_CHAIN': entityId = descriptor.ruleChainId?.id; break;
}
if (!entityId) {

2
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-install-dialog.component.ts

@ -75,7 +75,7 @@ export class TbIotHubInstallDialogComponent extends DialogComponent<TbIotHubInst
}
install(): void {
if (this.item.type === ItemType.CALCULATED_FIELD) {
if (this.item.type === ItemType.CALCULATED_FIELD || this.item.type === ItemType.ALARM_RULE) {
this.state = 'select-entity';
return;
}

4
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-installed-items-table.component.ts

@ -150,6 +150,7 @@ export class TbIotHubInstalledItemsTableComponent implements OnInit, OnChanges,
case 'DASHBOARD': return 'dashboard';
case 'SOLUTION_TEMPLATE': return 'integration_instructions';
case 'CALCULATED_FIELD': return 'functions';
case 'ALARM_RULE': return 'notification_important';
case 'RULE_CHAIN': return 'settings_ethernet';
case 'DEVICE': return 'memory';
default: return 'category';
@ -174,6 +175,7 @@ export class TbIotHubInstalledItemsTableComponent implements OnInit, OnChanges,
case 'WIDGET': return 'tb-type-widget';
case 'DASHBOARD': return 'tb-type-dashboard';
case 'CALCULATED_FIELD': return 'tb-type-calc-field';
case 'ALARM_RULE': return 'tb-type-alarm-rule';
case 'RULE_CHAIN': return 'tb-type-rule-chain';
case 'DEVICE': return 'tb-type-device';
case 'SOLUTION_TEMPLATE': return 'tb-type-solution-template';
@ -214,6 +216,7 @@ export class TbIotHubInstalledItemsTableComponent implements OnInit, OnChanges,
case 'WIDGET': return descriptor.widgetTypeId?.id;
case 'DASHBOARD': return descriptor.dashboardId?.id;
case 'CALCULATED_FIELD': return descriptor.calculatedFieldId?.id;
case 'ALARM_RULE': return descriptor.calculatedFieldId?.id;
case 'RULE_CHAIN': return descriptor.ruleChainId?.id;
case 'DEVICE': return descriptor.dashboardId?.id ?? null;
case 'SOLUTION_TEMPLATE': return descriptor.dashboardId?.id;
@ -227,6 +230,7 @@ export class TbIotHubInstalledItemsTableComponent implements OnInit, OnChanges,
case 'WIDGET': return EntityType.WIDGET_TYPE;
case 'DASHBOARD': return EntityType.DASHBOARD;
case 'CALCULATED_FIELD': return EntityType.CALCULATED_FIELD;
case 'ALARM_RULE': return EntityType.CALCULATED_FIELD;
case 'RULE_CHAIN': return EntityType.RULE_CHAIN;
case 'DEVICE': return descriptor.dashboardId ? EntityType.DASHBOARD : null;
case 'SOLUTION_TEMPLATE': return EntityType.DASHBOARD;

9
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-card.component.ts

@ -52,7 +52,9 @@ export class TbIotHubItemCardComponent {
) {}
isCompactLayout(): boolean {
return this.item.type === ItemType.CALCULATED_FIELD || this.item.type === ItemType.RULE_CHAIN;
return this.item.type === ItemType.CALCULATED_FIELD
|| this.item.type === ItemType.ALARM_RULE
|| this.item.type === ItemType.RULE_CHAIN;
}
getPreviewUrl(): string | null {
@ -71,6 +73,8 @@ export class TbIotHubItemCardComponent {
case ItemType.SOLUTION_TEMPLATE: return 'integration_instructions';
case ItemType.CALCULATED_FIELD:
return this.item.icon || cfTypeIcons.get(this.item.dataDescriptor?.cfType) || 'functions';
case ItemType.ALARM_RULE:
return this.item.icon || 'notification_important';
case ItemType.RULE_CHAIN:
return this.item.icon || (this.item.dataDescriptor?.ruleChainType === 'EDGE' ? 'router' : 'device_hub');
case ItemType.DEVICE: return 'memory';
@ -85,7 +89,8 @@ export class TbIotHubItemCardComponent {
const key = wt ? widgetTypeTranslations.get(wt) : null;
return key ? this.translate.instant(key) : wt || '';
}
case ItemType.CALCULATED_FIELD: {
case ItemType.CALCULATED_FIELD:
case ItemType.ALARM_RULE: {
const cfType = this.item.dataDescriptor?.cfType;
const key = cfType ? cfTypeTranslations.get(cfType) : null;
return key ? this.translate.instant(key) : cfType || '';

13
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-detail-dialog.component.ts

@ -85,7 +85,9 @@ export class TbIotHubItemDetailDialogComponent extends DialogComponent<TbIotHubI
}
isCompactLayout(): boolean {
return this.item.type === ItemType.CALCULATED_FIELD || this.item.type === ItemType.RULE_CHAIN;
return this.item.type === ItemType.CALCULATED_FIELD
|| this.item.type === ItemType.ALARM_RULE
|| this.item.type === ItemType.RULE_CHAIN;
}
getPreviewUrl(): string | null {
@ -107,6 +109,7 @@ export class TbIotHubItemDetailDialogComponent extends DialogComponent<TbIotHubI
case ItemType.DASHBOARD: return 'dashboard';
case ItemType.SOLUTION_TEMPLATE: return 'integration_instructions';
case ItemType.CALCULATED_FIELD: return 'functions';
case ItemType.ALARM_RULE: return 'notification_important';
case ItemType.RULE_CHAIN: return 'account_tree';
case ItemType.DEVICE: return 'memory';
default: return 'category';
@ -121,6 +124,9 @@ export class TbIotHubItemDetailDialogComponent extends DialogComponent<TbIotHubI
const cfType = this.item.dataDescriptor?.cfType;
return cfTypeIcons.get(cfType) || 'functions';
}
if (this.item.type === ItemType.ALARM_RULE) {
return 'notification_important';
}
switch (this.item.dataDescriptor?.ruleChainType) {
case 'CORE': return 'device_hub';
case 'EDGE': return 'router';
@ -129,7 +135,7 @@ export class TbIotHubItemDetailDialogComponent extends DialogComponent<TbIotHubI
}
getCompactSubtypeLabel(): string {
if (this.item.type === ItemType.CALCULATED_FIELD) {
if (this.item.type === ItemType.CALCULATED_FIELD || this.item.type === ItemType.ALARM_RULE) {
const cfType = this.item.dataDescriptor?.cfType;
const key = cfType ? cfTypeTranslations.get(cfType) : null;
return key ? this.translate.instant(key) : cfType || '';
@ -142,7 +148,7 @@ export class TbIotHubItemDetailDialogComponent extends DialogComponent<TbIotHubI
getSubtypeLabel(): string {
switch (this.item.type) {
case ItemType.CALCULATED_FIELD:
return this.getCompactSubtypeLabel();
case ItemType.ALARM_RULE:
case ItemType.RULE_CHAIN:
return this.getCompactSubtypeLabel();
case ItemType.WIDGET: {
@ -213,6 +219,7 @@ export class TbIotHubItemDetailDialogComponent extends DialogComponent<TbIotHubI
case 'WIDGET': entityId = descriptor.widgetTypeId?.id; entityType = EntityType.WIDGET_TYPE; break;
case 'DASHBOARD': entityId = descriptor.dashboardId?.id; entityType = EntityType.DASHBOARD; break;
case 'CALCULATED_FIELD':
case 'ALARM_RULE':
entityId = descriptor.calculatedFieldId?.id;
entityType = EntityType.CALCULATED_FIELD;
break;

1
ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-deep-link.utils.ts

@ -29,6 +29,7 @@ export function typeSegment(t: ItemType): string | undefined {
case ItemType.DASHBOARD: return 'dashboards';
case ItemType.SOLUTION_TEMPLATE: return 'solution-templates';
case ItemType.CALCULATED_FIELD: return 'calculated-fields';
case ItemType.ALARM_RULE: return 'alarm-rules';
case ItemType.RULE_CHAIN: return 'rule-chains';
case ItemType.DEVICE: return 'devices';
default: return undefined;

56
ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.html

@ -56,7 +56,7 @@
(mouseenter)="onHeroTypeHover(ht)"
(mouseleave)="onHeroTypeLeave()"
(click)="navigateToBrowse(ht.type)"
(keyup.enter)="navigateToBrowse(ht.type)">{{ ht.labelKey | translate }}@if (!last) {,}</span>
(keyup.enter)="navigateToBrowse(ht.type)">@if (last) {& }{{ ht.labelKey | translate }}@if (!last) {,}</span>
@if (i === 2) {
<span class="tb-iot-hub-hero-subtitle-break"></span>
}
@ -178,47 +178,48 @@
</div>
}
<!-- Popular Dashboards -->
@if (popularDashboards.length) {
<!-- Popular Solution Templates -->
@if (popularSolutionTemplates.length) {
<div class="tb-iot-hub-section">
<button mat-button class="tb-iot-hub-section-header" (click)="navigateToBrowse(ItemType.DASHBOARD)">
{{ 'iot-hub.popular-dashboards' | translate }}
<button mat-button class="tb-iot-hub-section-header" (click)="navigateToBrowse(ItemType.SOLUTION_TEMPLATE)">
{{ 'iot-hub.popular-solution-templates' | translate }}
<mat-icon iconPositionEnd>chevron_right</mat-icon>
</button>
<div class="tb-iot-hub-big-cards-row">
@for (item of popularDashboards; track item.id) {
@for (item of popularSolutionTemplates; track item.id) {
<tb-iot-hub-item-card
[item]="item"
[installedItem]="getInstalledSolutionTemplate(item)"
[showTypeChip]="false"
[showCreator]="true"
(cardClick)="openItemDetail($event)"
(creatorClick)="navigateToCreator($event)"
(installClick)="installItem($event)">
(installClick)="installItem($event)"
(updateClick)="updateItem($event)"
(deleteClick)="deleteInstalledItem($event)">
</tb-iot-hub-item-card>
}
</div>
</div>
}
<!-- Popular Solution Templates -->
@if (popularSolutionTemplates.length) {
<!-- Popular Devices -->
@if (popularDevices.length) {
<div class="tb-iot-hub-section">
<button mat-button class="tb-iot-hub-section-header" (click)="navigateToBrowse(ItemType.SOLUTION_TEMPLATE)">
{{ 'iot-hub.popular-solution-templates' | translate }}
<button mat-button class="tb-iot-hub-section-header" (click)="navigateToBrowse(ItemType.DEVICE)">
{{ 'iot-hub.popular-devices' | translate }}
<mat-icon iconPositionEnd>chevron_right</mat-icon>
</button>
<div class="tb-iot-hub-big-cards-row">
@for (item of popularSolutionTemplates; track item.id) {
@for (item of popularDevices; track item.id) {
<tb-iot-hub-item-card
[item]="item"
[installedItem]="getInstalledSolutionTemplate(item)"
[installedItemsCount]="findInstalledItemsCount(item)"
[showTypeChip]="false"
[showCreator]="true"
(cardClick)="openItemDetail($event)"
(creatorClick)="navigateToCreator($event)"
(installClick)="installItem($event)"
(updateClick)="updateItem($event)"
(deleteClick)="deleteInstalledItem($event)">
(installClick)="installItem($event)">
</tb-iot-hub-item-card>
}
</div>
@ -247,15 +248,15 @@
</div>
}
<!-- Popular Rule Chains -->
@if (popularRuleChains.length) {
<!-- Popular Alarm Rules -->
@if (popularAlarmRules.length) {
<div class="tb-iot-hub-section">
<button mat-button class="tb-iot-hub-section-header" (click)="navigateToBrowse(ItemType.RULE_CHAIN)">
{{ 'iot-hub.popular-rule-chains' | translate }}
<button mat-button class="tb-iot-hub-section-header" (click)="navigateToBrowse(ItemType.ALARM_RULE)">
{{ 'iot-hub.popular-alarm-rules' | translate }}
<mat-icon iconPositionEnd>chevron_right</mat-icon>
</button>
<div class="tb-iot-hub-compact-cards-grid">
@for (item of popularRuleChains; track item.id) {
@for (item of popularAlarmRules; track item.id) {
<tb-iot-hub-item-card
[item]="item"
[showTypeChip]="false"
@ -269,18 +270,17 @@
</div>
}
<!-- Popular Devices -->
@if (popularDevices.length) {
<!-- Popular Rule Chains -->
@if (popularRuleChains.length) {
<div class="tb-iot-hub-section">
<button mat-button class="tb-iot-hub-section-header" (click)="navigateToBrowse(ItemType.DEVICE)">
{{ 'iot-hub.popular-devices' | translate }}
<button mat-button class="tb-iot-hub-section-header" (click)="navigateToBrowse(ItemType.RULE_CHAIN)">
{{ 'iot-hub.popular-rule-chains' | translate }}
<mat-icon iconPositionEnd>chevron_right</mat-icon>
</button>
<div class="tb-iot-hub-big-cards-row">
@for (item of popularDevices; track item.id) {
<div class="tb-iot-hub-compact-cards-grid">
@for (item of popularRuleChains; track item.id) {
<tb-iot-hub-item-card
[item]="item"
[installedItemsCount]="findInstalledItemsCount(item)"
[showTypeChip]="false"
[showCreator]="true"
(cardClick)="openItemDetail($event)"

4
ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.scss

@ -499,6 +499,10 @@
background: linear-gradient(104.75deg, rgb(245, 252, 255) 0%, rgb(149, 222, 248) 100%);
}
.category-alarm-rules {
background: linear-gradient(104.75deg, rgb(245, 246, 255) 0%, rgb(189, 197, 255) 100%);
}
.category-rule-chains {
background: linear-gradient(104.75deg, rgb(255, 252, 245) 0%, rgb(255, 238, 194) 100%);
}

70
ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.ts

@ -80,30 +80,30 @@ export class TbIotHubHomeComponent implements OnInit, OnDestroy {
gradientColor: 'rgba(44, 151, 85, 0.1)',
icons: ['assets/iot-hub/hero-widget-icon-1.svg', 'assets/iot-hub/hero-widget-icon-2.svg', 'assets/iot-hub/hero-widget-icon-3.svg', 'assets/iot-hub/hero-widget-icon-4.svg']
},
{
type: ItemType.DASHBOARD, labelKey: 'item.type-dashboard-plural', color: '#4d5fd0',
gradientColor: 'rgba(77, 95, 208, 0.1)',
icons: ['assets/iot-hub/hero-dashboard-icon-1.svg', 'assets/iot-hub/hero-dashboard-icon-2.svg', 'assets/iot-hub/hero-dashboard-icon-3.svg', 'assets/iot-hub/hero-dashboard-icon-4.svg']
},
{
type: ItemType.SOLUTION_TEMPLATE, labelKey: 'item.type-solution-template-plural', color: '#2666a9',
gradientColor: 'rgba(38, 102, 169, 0.1)',
icons: ['assets/iot-hub/hero-solution-template-icon-1.svg', 'assets/iot-hub/hero-solution-template-icon-2.svg', 'assets/iot-hub/hero-solution-template-icon-3.svg', 'assets/iot-hub/hero-solution-template-icon-4.svg']
},
{
type: ItemType.DEVICE, labelKey: 'item.type-device-plural', color: '#4b8a79',
gradientColor: 'rgba(75, 138, 121, 0.1)',
icons: ['assets/iot-hub/hero-device-icon-1.svg', 'assets/iot-hub/hero-device-icon-2.svg', 'assets/iot-hub/hero-device-icon-3.svg', 'assets/iot-hub/hero-device-icon-4.svg']
},
{
type: ItemType.CALCULATED_FIELD, labelKey: 'item.type-calculated-field-plural', color: '#006d92',
gradientColor: 'rgba(0, 109, 146, 0.1)',
icons: ['assets/iot-hub/hero-calculated-field-icon-1.svg', 'assets/iot-hub/hero-calculated-field-icon-2.svg', 'assets/iot-hub/hero-calculated-field-icon-3.svg', 'assets/iot-hub/hero-calculated-field-icon-4.svg']
},
{
type: ItemType.ALARM_RULE, labelKey: 'item.type-alarm-rule-plural', color: '#4d5fd0',
gradientColor: 'rgba(77, 95, 208, 0.1)',
icons: ['assets/iot-hub/hero-dashboard-icon-1.svg', 'assets/iot-hub/hero-dashboard-icon-2.svg', 'assets/iot-hub/hero-dashboard-icon-3.svg', 'assets/iot-hub/hero-dashboard-icon-4.svg']
},
{
type: ItemType.RULE_CHAIN, labelKey: 'item.type-rule-chain-plural', color: '#95694b',
gradientColor: 'rgba(149, 105, 75, 0.1)',
icons: ['assets/iot-hub/hero-rule-chain-icon-1.svg', 'assets/iot-hub/hero-rule-chain-icon-2.svg', 'assets/iot-hub/hero-rule-chain-icon-3.svg', 'assets/iot-hub/hero-rule-chain-icon-4.svg']
},
{
type: ItemType.DEVICE, labelKey: 'iot-hub.and-devices', color: '#4b8a79',
gradientColor: 'rgba(75, 138, 121, 0.1)',
icons: ['assets/iot-hub/hero-device-icon-1.svg', 'assets/iot-hub/hero-device-icon-2.svg', 'assets/iot-hub/hero-device-icon-3.svg', 'assets/iot-hub/hero-device-icon-4.svg']
}
];
@ -113,25 +113,25 @@ export class TbIotHubHomeComponent implements OnInit, OnDestroy {
categoryCards: CategoryCard[] = [
{ type: ItemType.WIDGET, titleKey: 'item.type-widget-plural', icon: 'widgets', cssClass: 'category-widgets', image: 'assets/iot-hub/category-widgets-img.svg' },
{ type: ItemType.DASHBOARD, titleKey: 'item.type-dashboard-plural', icon: 'dashboard', cssClass: 'category-dashboards', image: 'assets/iot-hub/category-dashboards-img.svg' },
{ type: ItemType.SOLUTION_TEMPLATE, titleKey: 'item.type-solution-template-plural', icon: 'integration_instructions', cssClass: 'category-solutions', image: 'assets/iot-hub/category-solution-templates-img.png' },
{ type: ItemType.DEVICE, titleKey: 'iot-hub.device-library', icon: 'memory', cssClass: 'category-devices', image: 'assets/iot-hub/category-device-library-img.svg' },
{ type: ItemType.CALCULATED_FIELD, titleKey: 'item.type-calculated-field-plural', icon: 'functions', cssClass: 'category-calc-fields', image: 'assets/iot-hub/category-calculated-fields-img.svg' },
{ type: ItemType.RULE_CHAIN, titleKey: 'item.type-rule-chain-plural', icon: 'account_tree', cssClass: 'category-rule-chains', image: 'assets/iot-hub/category-rule-chains-img.svg' },
{ type: ItemType.DEVICE, titleKey: 'iot-hub.device-library', icon: 'memory', cssClass: 'category-devices', image: 'assets/iot-hub/category-device-library-img.svg' }
{ type: ItemType.ALARM_RULE, titleKey: 'item.type-alarm-rule-plural', icon: 'notification_important', cssClass: 'category-alarm-rules', image: 'assets/iot-hub/category-dashboards-img.svg' },
{ type: ItemType.RULE_CHAIN, titleKey: 'item.type-rule-chain-plural', icon: 'account_tree', cssClass: 'category-rule-chains', image: 'assets/iot-hub/category-rule-chains-img.svg' }
];
popularWidgets: MpItemVersionView[] = [];
popularDashboards: MpItemVersionView[] = [];
popularSolutionTemplates: MpItemVersionView[] = [];
popularCalcFields: MpItemVersionView[] = [];
popularAlarmRules: MpItemVersionView[] = [];
popularRuleChains: MpItemVersionView[] = [];
popularDevices: MpItemVersionView[] = [];
installedWidgets: IotHubInstalledItem[] = [];
installedSolutionTemplates: IotHubInstalledItem[] = [];
installedDeviceCounts: Record<string, number> = {};
installedDashboardCounts: Record<string, number> = {};
installedCalcFieldCounts: Record<string, number> = {};
installedAlarmRuleCounts: Record<string, number> = {};
installedRuleChainCounts: Record<string, number> = {};
installedItemsCount = 0;
@ -260,11 +260,21 @@ export class TbIotHubHomeComponent implements OnInit, OnDestroy {
}
isCompactType(type: ItemType): boolean {
return type === ItemType.CALCULATED_FIELD || type === ItemType.RULE_CHAIN;
return type === ItemType.CALCULATED_FIELD
|| type === ItemType.ALARM_RULE
|| type === ItemType.RULE_CHAIN;
}
getCompactIcon(item: MpItemVersionView): string {
return item.icon || (item.type === ItemType.CALCULATED_FIELD ? 'functions' : 'settings_ethernet');
if (item.icon) {
return item.icon;
}
switch (item.type) {
case ItemType.CALCULATED_FIELD: return 'functions';
case ItemType.ALARM_RULE: return 'notification_important';
case ItemType.RULE_CHAIN: return 'settings_ethernet';
default: return 'category';
}
}
getItemImage(item: MpItemVersionView): string | null {
@ -276,6 +286,7 @@ export class TbIotHubHomeComponent implements OnInit, OnDestroy {
case ItemType.WIDGET: return 'widgets';
case ItemType.DASHBOARD: return 'dashboard';
case ItemType.SOLUTION_TEMPLATE: return 'integration_instructions';
case ItemType.ALARM_RULE: return 'notification_important';
case ItemType.DEVICE: return 'memory';
default: return 'category';
}
@ -296,6 +307,7 @@ export class TbIotHubHomeComponent implements OnInit, OnDestroy {
case ItemType.DASHBOARD: return 'dashboards';
case ItemType.SOLUTION_TEMPLATE: return 'solution-templates';
case ItemType.CALCULATED_FIELD: return 'calculated-fields';
case ItemType.ALARM_RULE: return 'alarm-rules';
case ItemType.RULE_CHAIN: return 'rule-chains';
case ItemType.DEVICE: return 'devices';
default: return 'widgets';
@ -332,14 +344,14 @@ export class TbIotHubHomeComponent implements OnInit, OnDestroy {
this.iotHubApiService.getInstalledItemCounts(ItemType.DEVICE, config).subscribe(counts => {
this.installedDeviceCounts = counts;
});
} else if (type === ItemType.DASHBOARD) {
this.iotHubApiService.getInstalledItemCounts(ItemType.DASHBOARD, config).subscribe(counts => {
this.installedDashboardCounts = counts;
});
} else if (type === ItemType.CALCULATED_FIELD) {
this.iotHubApiService.getInstalledItemCounts(ItemType.CALCULATED_FIELD, config).subscribe(counts => {
this.installedCalcFieldCounts = counts;
});
} else if (type === ItemType.ALARM_RULE) {
this.iotHubApiService.getInstalledItemCounts(ItemType.ALARM_RULE, config).subscribe(counts => {
this.installedAlarmRuleCounts = counts;
});
} else if (type === ItemType.RULE_CHAIN) {
this.iotHubApiService.getInstalledItemCounts(ItemType.RULE_CHAIN, config).subscribe(counts => {
this.installedRuleChainCounts = counts;
@ -362,10 +374,10 @@ export class TbIotHubHomeComponent implements OnInit, OnDestroy {
switch (item.type) {
case ItemType.DEVICE:
return this.installedDeviceCounts[item.itemId] || 0;
case ItemType.DASHBOARD:
return this.installedDashboardCounts[item.itemId] || 0;
case ItemType.CALCULATED_FIELD:
return this.installedCalcFieldCounts[item.itemId] || 0;
case ItemType.ALARM_RULE:
return this.installedAlarmRuleCounts[item.itemId] || 0;
case ItemType.RULE_CHAIN:
return this.installedRuleChainCounts[item.itemId] || 0;
default:
@ -414,10 +426,10 @@ export class TbIotHubHomeComponent implements OnInit, OnDestroy {
this.installedSolutionTemplates = this.installedSolutionTemplates.filter(i => i.id.id !== installedItem.id.id);
} else if (item.type === ItemType.DEVICE && this.installedDeviceCounts[item.itemId]) {
this.installedDeviceCounts[item.itemId] = Math.max(0, this.installedDeviceCounts[item.itemId] - 1);
} else if (item.type === ItemType.DASHBOARD && this.installedDashboardCounts[item.itemId]) {
this.installedDashboardCounts[item.itemId] = Math.max(0, this.installedDashboardCounts[item.itemId] - 1);
} else if (item.type === ItemType.CALCULATED_FIELD && this.installedCalcFieldCounts[item.itemId]) {
this.installedCalcFieldCounts[item.itemId] = Math.max(0, this.installedCalcFieldCounts[item.itemId] - 1);
} else if (item.type === ItemType.ALARM_RULE && this.installedAlarmRuleCounts[item.itemId]) {
this.installedAlarmRuleCounts[item.itemId] = Math.max(0, this.installedAlarmRuleCounts[item.itemId] - 1);
} else if (item.type === ItemType.RULE_CHAIN && this.installedRuleChainCounts[item.itemId]) {
this.installedRuleChainCounts[item.itemId] = Math.max(0, this.installedRuleChainCounts[item.itemId] - 1);
}
@ -472,31 +484,31 @@ export class TbIotHubHomeComponent implements OnInit, OnDestroy {
forkJoin({
widgets: this.iotHubApiService.getPublishedVersions(buildQuery(ItemType.WIDGET, this.bigCardCount), config),
dashboards: this.iotHubApiService.getPublishedVersions(buildQuery(ItemType.DASHBOARD, this.bigCardCount), config),
solutionTemplates: this.iotHubApiService.getPublishedVersions(buildQuery(ItemType.SOLUTION_TEMPLATE, this.bigCardCount), config),
calcFields: this.iotHubApiService.getPublishedVersions(buildQuery(ItemType.CALCULATED_FIELD, this.compactCardCount), config),
alarmRules: this.iotHubApiService.getPublishedVersions(buildQuery(ItemType.ALARM_RULE, this.compactCardCount), config),
ruleChains: this.iotHubApiService.getPublishedVersions(buildQuery(ItemType.RULE_CHAIN, this.compactCardCount), config),
devices: this.iotHubApiService.getPublishedVersions(buildQuery(ItemType.DEVICE, this.bigCardCount), config),
installedWidgets: this.iotHubApiService.getInstalledItems(installedPageLink, ItemType.WIDGET, undefined, config),
installedSolutionTemplates: this.iotHubApiService.getInstalledItems(installedPageLink, ItemType.SOLUTION_TEMPLATE, undefined, config),
installedDeviceCounts: this.iotHubApiService.getInstalledItemCounts(ItemType.DEVICE, config),
installedDashboardCounts: this.iotHubApiService.getInstalledItemCounts(ItemType.DASHBOARD, config),
installedCalcFieldCounts: this.iotHubApiService.getInstalledItemCounts(ItemType.CALCULATED_FIELD, config),
installedAlarmRuleCounts: this.iotHubApiService.getInstalledItemCounts(ItemType.ALARM_RULE, config),
installedRuleChainCounts: this.iotHubApiService.getInstalledItemCounts(ItemType.RULE_CHAIN, config),
installedCount: this.iotHubApiService.getInstalledItemsCount(null, config)
}).subscribe({
next: (results) => {
this.popularWidgets = results.widgets.data;
this.popularDashboards = results.dashboards.data;
this.popularSolutionTemplates = results.solutionTemplates.data;
this.popularCalcFields = results.calcFields.data;
this.popularAlarmRules = results.alarmRules.data;
this.popularRuleChains = results.ruleChains.data;
this.popularDevices = results.devices.data;
this.installedWidgets = results.installedWidgets.data;
this.installedSolutionTemplates = results.installedSolutionTemplates.data;
this.installedDeviceCounts = results.installedDeviceCounts;
this.installedDashboardCounts = results.installedDashboardCounts;
this.installedCalcFieldCounts = results.installedCalcFieldCounts;
this.installedAlarmRuleCounts = results.installedAlarmRuleCounts;
this.installedRuleChainCounts = results.installedRuleChainCounts;
this.installedItemsCount = results.installedCount;
this.isLoading = false;

7
ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-items-page.component.ts

@ -63,6 +63,13 @@ const PAGE_CONFIGS: Record<string, ItemTypePageConfig> = {
image: 'assets/iot-hub/items-page-calculated-fields-hero.svg',
routeSegment: 'calculated-fields'
},
ALARM_RULE: {
type: ItemType.ALARM_RULE,
titleKey: 'item.type-alarm-rule-plural',
descriptionKey: 'iot-hub.items-page-desc-alarm-rules',
image: 'assets/iot-hub/items-page-dashboards-hero.svg',
routeSegment: 'alarm-rules'
},
RULE_CHAIN: {
type: ItemType.RULE_CHAIN,
titleKey: 'item.type-rule-chain-plural',

10
ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-routing.module.ts

@ -84,6 +84,16 @@ const routes: Routes = [
breadcrumb: { label: 'item.type-calculated-field-plural', icon: 'functions' }
}
},
{
path: 'alarm-rules',
component: TbIotHubItemsPageComponent,
data: {
auth: [Authority.TENANT_ADMIN],
title: 'item.type-alarm-rule-plural',
itemType: 'ALARM_RULE',
breadcrumb: { label: 'item.type-alarm-rule-plural', icon: 'notification_important' }
}
},
{
path: 'rule-chains',
component: TbIotHubItemsPageComponent,

7
ui-ngx/src/app/shared/models/iot-hub/iot-hub-installed-item.models.ts

@ -32,6 +32,12 @@ export interface CalculatedFieldInstalledItemDescriptor {
entityId: { entityType: string; id: string };
}
export interface AlarmRuleInstalledItemDescriptor {
type: 'ALARM_RULE';
calculatedFieldId: { id: string };
entityId: { entityType: string; id: string };
}
export interface RuleChainInstalledItemDescriptor {
type: 'RULE_CHAIN';
ruleChainId: { id: string };
@ -58,6 +64,7 @@ export type IotHubInstalledItemDescriptor =
| WidgetInstalledItemDescriptor
| DashboardInstalledItemDescriptor
| CalculatedFieldInstalledItemDescriptor
| AlarmRuleInstalledItemDescriptor
| RuleChainInstalledItemDescriptor
| DeviceInstalledItemDescriptor
| SolutionTemplateInstalledItemDescriptor;

17
ui-ngx/src/app/shared/models/iot-hub/iot-hub-item.models.ts

@ -19,6 +19,7 @@ export enum ItemType {
DASHBOARD = 'DASHBOARD',
SOLUTION_TEMPLATE = 'SOLUTION_TEMPLATE',
CALCULATED_FIELD = 'CALCULATED_FIELD',
ALARM_RULE = 'ALARM_RULE',
RULE_CHAIN = 'RULE_CHAIN',
DEVICE = 'DEVICE'
}
@ -29,11 +30,27 @@ export const itemTypeTranslations = new Map<ItemType, string>(
[ItemType.DASHBOARD, 'item.type-dashboard'],
[ItemType.SOLUTION_TEMPLATE, 'item.type-solution-template'],
[ItemType.CALCULATED_FIELD, 'item.type-calculated-field'],
[ItemType.ALARM_RULE, 'item.type-alarm-rule'],
[ItemType.RULE_CHAIN, 'item.type-rule-chain'],
[ItemType.DEVICE, 'item.type-device']
]
);
/**
* Item types discoverable to creators in the marketplace UI.
* DASHBOARD is intentionally absent (IoT Hub no longer accepts Dashboard contributions).
* Defensive code paths (item card, detail dialog descriptor switch, installed-items table,
* install handler, /iot-hub/dashboards route) remain functional for already-installed items.
*/
export const CREATOR_VISIBLE_ITEM_TYPES: ItemType[] = [
ItemType.WIDGET,
ItemType.SOLUTION_TEMPLATE,
ItemType.DEVICE,
ItemType.CALCULATED_FIELD,
ItemType.ALARM_RULE,
ItemType.RULE_CHAIN,
];
export interface FilterParamInfo {
key: string;
totalItems: number;

7
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -4183,6 +4183,8 @@
"type-solution-template-plural": "Solution Templates",
"type-calculated-field": "Calculated Field",
"type-calculated-field-plural": "Calculated Fields",
"type-alarm-rule": "Alarm Rule",
"type-alarm-rule-plural": "Alarm Rules",
"type-rule-chain": "Rule Chain",
"type-rule-chain-plural": "Rule Chains",
"type-device": "Device",
@ -4223,6 +4225,7 @@
"items-page-desc-dashboards": "Explore dashboards to find pre-built visualization templates and interactive UI layouts you can deploy in minutes to bring your IoT data to life.",
"items-page-desc-solution-templates": "Complete IoT solution packages with dashboards, rule chains, and device configurations. Get started with proven architectures.",
"items-page-desc-calculated-fields": "Use pre-configured Calculated Fields to automate complex metrics like fuel efficiency or power factor. Skip the manual logic and keep your dashboards clean and actionable.",
"items-page-desc-alarm-rules": "Use pre-built Alarm Rule templates to detect critical conditions like low battery, threshold breaches, or devices going offline. Skip writing the rule logic and start reacting to incidents the moment they happen.",
"items-page-desc-rule-chains": "From sophisticated data processing to seamless API integrations, Rule Chain templates provide the architectural foundation you need to scale without building from scratch.",
"items-page-desc-devices": "Explore the device library to find pre-configured connectivity templates you can deploy in minutes to connect your hardware instantly.",
"device-library": "Device Library",
@ -4230,6 +4233,7 @@
"popular-dashboards": "Popular Dashboards",
"popular-solution-templates": "Popular Solution Templates",
"popular-calculated-fields": "Popular Calculated Fields",
"popular-alarm-rules": "Popular Alarm Rules",
"popular-rule-chains": "Popular Rule Chains",
"popular-devices": "Popular Devices",
"become-creator-text": "Submit your templates to the ThingsBoard IoT Hub to get featured and showcase your solutions to our global community.",
@ -4238,6 +4242,7 @@
"title-widgets": "Widgets",
"title-dashboards": "Dashboards",
"title-calculated-fields": "Calculated Fields",
"title-alarm-rules": "Alarm Rules",
"title-rule-chains": "Rule Chains",
"title-devices": "Devices",
"title-solution-templates": "Solution Templates",
@ -4245,12 +4250,14 @@
"subtitle-dashboards": "Browse ready-to-use dashboards",
"subtitle-solution-templates": "Browse complete IoT solution packages",
"subtitle-calculated-fields": "Browse calculated field templates",
"subtitle-alarm-rules": "Browse alarm rule templates",
"subtitle-rule-chains": "Browse rule chain templates",
"subtitle-devices": "Browse device configurations",
"search-widgets": "Search widgets...",
"search-dashboards": "Search dashboards...",
"search-solution-templates": "Search solution templates...",
"search-calculated-fields": "Search calculated fields...",
"search-alarm-rules": "Search alarm rules...",
"search-rule-chains": "Search rule chains...",
"search-devices": "Search devices...",
"sort-most-installed": "Most installed",

Loading…
Cancel
Save