Browse Source

Merge branch 'master' into feature/time-series-chart

pull/10315/head
Igor Kulikov 3 years ago
parent
commit
915f670bef
  1. 2
      application/src/main/data/json/system/widget_types/signal_strength.json
  2. 9
      application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
  3. 4
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainManagerActor.java
  4. 4
      application/src/main/java/org/thingsboard/server/actors/service/ContextAwareActor.java
  5. 2
      application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java
  6. 11
      application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
  7. 25
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
  8. 1
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/BaseAssetProfileProcessor.java
  9. 13
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/BaseDeviceProcessor.java
  10. 21
      common/actor/src/main/java/org/thingsboard/server/actors/DefaultTbActorSystem.java
  11. 2
      common/actor/src/main/java/org/thingsboard/server/actors/TbActor.java
  12. 2
      common/actor/src/main/java/org/thingsboard/server/actors/TbActorCtx.java
  13. 9
      common/actor/src/main/java/org/thingsboard/server/actors/TbActorMailbox.java
  14. 2
      common/actor/src/main/java/org/thingsboard/server/actors/TbActorSystem.java
  15. 29
      common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java
  16. 1
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
  17. 20
      dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceTest.java
  18. 2
      msa/js-executor/api/jsInvokeMessageProcessor.ts
  19. 4
      ui-ngx/src/app/core/http/public-api.ts
  20. 10
      ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.ts
  21. 1
      ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html
  22. 4
      ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.component.ts
  23. 2
      ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.models.ts
  24. 6
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.html
  25. 18
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.ts
  26. 87
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.html
  27. 8
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.scss
  28. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.ts
  29. 7
      ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/signal-strength-widget-settings.component.html
  30. 3
      ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/signal-strength-widget-settings.component.ts
  31. 12
      ui-ngx/src/app/modules/home/models/services.map.ts
  32. 3
      ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html
  33. 28
      ui-ngx/src/app/modules/home/pages/notification/sent/sent-notification-dialog.component.html
  34. 9
      ui-ngx/src/app/modules/home/pages/notification/sent/sent-notification-dialog.component.scss
  35. 2
      ui-ngx/src/app/modules/home/pages/notification/sent/sent-notification-dialog.componet.ts
  36. 2
      ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.scss
  37. 18
      ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.html
  38. 1
      ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.ts
  39. 2
      ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.html
  40. 9
      ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.ts
  41. 19
      ui-ngx/src/app/shared/components/image/image-dialog.component.ts
  42. 30
      ui-ngx/src/app/shared/components/image/upload-image-dialog.component.ts
  43. 2
      ui-ngx/src/app/shared/models/alarm.models.ts
  44. 12
      ui-ngx/src/app/shared/models/constants.ts
  45. 4
      ui-ngx/src/app/shared/models/resource.models.ts
  46. 7
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  47. 4
      ui-ngx/src/assets/locale/locale.constant-ko_KR.json
  48. 4
      ui-ngx/src/assets/locale/locale.constant-sl_SI.json

2
application/src/main/data/json/system/widget_types/signal_strength.json

@ -17,7 +17,7 @@
"settingsDirective": "tb-signal-strength-widget-settings", "settingsDirective": "tb-signal-strength-widget-settings",
"hasBasicMode": true, "hasBasicMode": true,
"basicModeDirective": "tb-signal-strength-basic-config", "basicModeDirective": "tb-signal-strength-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"rssi\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"if (!prevValue) {\\n prevValue = Math.random() * -96;\\n}\\nvar value = prevValue + (Math.random() * 60 - 30);\\nif (value > 0) {\\n\\tvalue = 0;\\n} else if (value < -96) {\\n value = -96;\\n}\\nlet rand = Math.random();\\nreturn rand < 0.2 ? (rand < 0.1 ? -101 : '') : value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"layout\":\"wifi\",\"showDate\":false,\"dateFormat\":{\"format\":null,\"lastUpdateAgo\":true,\"custom\":false},\"dateFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"dateColor\":\"rgba(0, 0, 0, 0.38)\",\"activeBarsColor\":{\"color\":\"rgba(92, 223, 144, 1)\",\"type\":\"range\",\"rangeList\":[{\"to\":-85,\"color\":\"rgba(227, 71, 71, 1)\"},{\"from\":-85,\"to\":-70,\"color\":\"rgba(255, 122, 0, 1)\"},{\"from\":-70,\"to\":-55,\"color\":\"rgba(246, 206, 67, 1)\"},{\"from\":-55,\"color\":\"rgba(92, 223, 144, 1)\"}],\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"inactiveBarsColor\":\"rgba(224, 224, 224, 1)\",\"showTooltip\":true,\"showTooltipValue\":true,\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":13,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0,0,0,0.76)\",\"showTooltipDate\":true,\"tooltipDateFormat\":{\"format\":null,\"lastUpdateAgo\":true,\"custom\":false},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":13,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipDateColor\":\"rgba(0,0,0,0.76)\",\"tooltipBackgroundColor\":\"rgba(255,255,255,0.72)\",\"tooltipBackgroundBlur\":3,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Signal strength\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"dBm\",\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleIcon\":\"signal_cellular_alt\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"18px\",\"timewindowStyle\":{\"showIcon\":true,\"iconSize\":\"14px\",\"icon\":\"query_builder\",\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":\"1\"},\"color\":null},\"titleColor\":\"rgba(0, 0, 0, 0.87)\"}" "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"rssi\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"if (!prevValue) {\\n prevValue = Math.random() * -96;\\n}\\nvar value = prevValue + (Math.random() * 60 - 30);\\nif (value > 0) {\\n\\tvalue = 0;\\n} else if (value < -96) {\\n value = -96;\\n}\\nlet rand = Math.random();\\nreturn rand < 0.2 ? (rand < 0.1 ? -101 : '') : value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"layout\":\"wifi\",\"showDate\":false,\"dateFormat\":{\"format\":null,\"lastUpdateAgo\":true,\"custom\":false},\"dateFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"dateColor\":\"rgba(0, 0, 0, 0.38)\",\"activeBarsColor\":{\"color\":\"rgba(92, 223, 144, 1)\",\"type\":\"range\",\"rangeList\":[{\"to\":-85,\"color\":\"rgba(227, 71, 71, 1)\"},{\"from\":-85,\"to\":-70,\"color\":\"rgba(255, 122, 0, 1)\"},{\"from\":-70,\"to\":-55,\"color\":\"rgba(246, 206, 67, 1)\"},{\"from\":-55,\"color\":\"rgba(92, 223, 144, 1)\"}],\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"inactiveBarsColor\":\"rgba(224, 224, 224, 1)\",\"noSignalRssiValue\":-100,\"showTooltip\":true,\"showTooltipValue\":true,\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":13,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0,0,0,0.76)\",\"showTooltipDate\":true,\"tooltipDateFormat\":{\"format\":null,\"lastUpdateAgo\":true,\"custom\":false},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":13,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipDateColor\":\"rgba(0,0,0,0.76)\",\"tooltipBackgroundColor\":\"rgba(255,255,255,0.72)\",\"tooltipBackgroundBlur\":3,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Signal strength\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"dBm\",\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleIcon\":\"signal_cellular_alt\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"18px\",\"timewindowStyle\":{\"showIcon\":true,\"iconSize\":\"14px\",\"icon\":\"query_builder\",\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":\"1\"},\"color\":null},\"titleColor\":\"rgba(0, 0, 0, 0.87)\"}"
}, },
"externalId": null, "externalId": null,
"tags": [ "tags": [

9
application/src/main/java/org/thingsboard/server/actors/app/AppActor.java

@ -17,6 +17,7 @@ package org.thingsboard.server.actors.app;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.ProcessFailureStrategy;
import org.thingsboard.server.actors.TbActor; import org.thingsboard.server.actors.TbActor;
import org.thingsboard.server.actors.TbActorCtx; import org.thingsboard.server.actors.TbActorCtx;
import org.thingsboard.server.actors.TbActorException; import org.thingsboard.server.actors.TbActorException;
@ -88,7 +89,7 @@ public class AppActor extends ContextAwareActor {
case APP_INIT_MSG: case APP_INIT_MSG:
break; break;
case PARTITION_CHANGE_MSG: case PARTITION_CHANGE_MSG:
ctx.broadcastToChildren(msg); ctx.broadcastToChildren(msg, true);
break; break;
case COMPONENT_LIFE_CYCLE_MSG: case COMPONENT_LIFE_CYCLE_MSG:
onComponentLifecycleMsg((ComponentLifecycleMsg) msg); onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
@ -219,6 +220,12 @@ public class AppActor extends ContextAwareActor {
} }
} }
@Override
public ProcessFailureStrategy onProcessFailure(TbActorMsg msg, Throwable t) {
log.error("Failed to process msg: {}", msg, t);
return doProcessFailure(t);
}
public static class ActorCreator extends ContextBasedCreator { public static class ActorCreator extends ContextBasedCreator {
public ActorCreator(ActorSystemContext context) { public ActorCreator(ActorSystemContext context) {

4
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainManagerActor.java

@ -59,7 +59,7 @@ public abstract class RuleChainManagerActor extends ContextAwareActor {
} }
protected void initRuleChains() { protected void initRuleChains() {
ruleChainsInitialized = true; log.debug("[{}] Initializing rule chains", tenantId);
for (RuleChain ruleChain : new PageDataIterable<>(link -> ruleChainService.findTenantRuleChainsByType(tenantId, RuleChainType.CORE, link), ContextAwareActor.ENTITY_PACK_LIMIT)) { for (RuleChain ruleChain : new PageDataIterable<>(link -> ruleChainService.findTenantRuleChainsByType(tenantId, RuleChainType.CORE, link), ContextAwareActor.ENTITY_PACK_LIMIT)) {
RuleChainId ruleChainId = ruleChain.getId(); RuleChainId ruleChainId = ruleChain.getId();
log.debug("[{}|{}] Creating rule chain actor", ruleChainId.getEntityType(), ruleChain.getId()); log.debug("[{}|{}] Creating rule chain actor", ruleChainId.getEntityType(), ruleChain.getId());
@ -67,9 +67,11 @@ public abstract class RuleChainManagerActor extends ContextAwareActor {
visit(ruleChain, actorRef); visit(ruleChain, actorRef);
log.debug("[{}|{}] Rule Chain actor created.", ruleChainId.getEntityType(), ruleChainId.getId()); log.debug("[{}|{}] Rule Chain actor created.", ruleChainId.getEntityType(), ruleChainId.getId());
} }
ruleChainsInitialized = true;
} }
protected void destroyRuleChains() { protected void destroyRuleChains() {
log.debug("[{}] Destroying rule chains", tenantId);
for (RuleChain ruleChain : new PageDataIterable<>(link -> ruleChainService.findTenantRuleChainsByType(tenantId, RuleChainType.CORE, link), ContextAwareActor.ENTITY_PACK_LIMIT)) { for (RuleChain ruleChain : new PageDataIterable<>(link -> ruleChainService.findTenantRuleChainsByType(tenantId, RuleChainType.CORE, link), ContextAwareActor.ENTITY_PACK_LIMIT)) {
ctx.stop(new TbEntityActorId(ruleChain.getId())); ctx.stop(new TbEntityActorId(ruleChain.getId()));
} }

4
application/src/main/java/org/thingsboard/server/actors/service/ContextAwareActor.java

@ -47,8 +47,8 @@ public abstract class ContextAwareActor extends AbstractTbActor {
protected abstract boolean doProcess(TbActorMsg msg); protected abstract boolean doProcess(TbActorMsg msg);
@Override @Override
public ProcessFailureStrategy onProcessFailure(Throwable t) { public ProcessFailureStrategy onProcessFailure(TbActorMsg msg, Throwable t) {
log.debug("[{}] Processing failure: ", getActorRef().getActorId(), t); log.debug("[{}] Processing failure for msg {}", getActorRef().getActorId(), msg, t);
return doProcessFailure(t); return doProcessFailure(t);
} }

2
application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java

@ -122,7 +122,7 @@ public class DefaultActorService extends TbApplicationEventListener<PartitionCha
@Override @Override
protected void onTbApplicationEvent(PartitionChangeEvent event) { protected void onTbApplicationEvent(PartitionChangeEvent event) {
log.info("Received partition change event."); log.info("Received partition change event.");
this.appActor.tellWithHighPriority(new PartitionChangeMsg(event.getServiceType())); appActor.tellWithHighPriority(new PartitionChangeMsg(event.getServiceType()));
} }
@Override @Override

11
application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java

@ -17,6 +17,7 @@ package org.thingsboard.server.actors.tenant;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.ProcessFailureStrategy;
import org.thingsboard.server.actors.TbActor; import org.thingsboard.server.actors.TbActor;
import org.thingsboard.server.actors.TbActorCtx; import org.thingsboard.server.actors.TbActorCtx;
import org.thingsboard.server.actors.TbActorException; import org.thingsboard.server.actors.TbActorException;
@ -183,7 +184,7 @@ public class TenantActor extends RuleChainManagerActor {
return; return;
} }
TbMsg tbMsg = msg.getMsg(); TbMsg tbMsg = msg.getMsg();
if (getApiUsageState().isReExecEnabled() && ruleChainsInitialized) { if (getApiUsageState().isReExecEnabled()) {
if (tbMsg.getRuleChainId() == null) { if (tbMsg.getRuleChainId() == null) {
if (getRootChainActor() != null) { if (getRootChainActor() != null) {
getRootChainActor().tell(msg); getRootChainActor().tell(msg);
@ -207,7 +208,7 @@ public class TenantActor extends RuleChainManagerActor {
} }
private void onRuleChainMsg(RuleChainAwareMsg msg) { private void onRuleChainMsg(RuleChainAwareMsg msg) {
if (getApiUsageState().isReExecEnabled() && ruleChainsInitialized) { if (getApiUsageState().isReExecEnabled()) {
getOrCreateActor(msg.getRuleChainId()).tell(msg); getOrCreateActor(msg.getRuleChainId()).tell(msg);
} }
} }
@ -319,6 +320,12 @@ public class TenantActor extends RuleChainManagerActor {
return apiUsageState; return apiUsageState;
} }
@Override
public ProcessFailureStrategy onProcessFailure(TbActorMsg msg, Throwable t) {
log.error("[{}] Failed to process msg: {}", tenantId, msg, t);
return doProcessFailure(t);
}
public static class ActorCreator extends ContextBasedCreator { public static class ActorCreator extends ContextBasedCreator {
private final TenantId tenantId; private final TenantId tenantId;

25
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java

@ -696,11 +696,6 @@ public final class EdgeGrpcSession implements Closeable {
private ListenableFuture<List<Void>> processUplinkMsg(UplinkMsg uplinkMsg) { private ListenableFuture<List<Void>> processUplinkMsg(UplinkMsg uplinkMsg) {
List<ListenableFuture<Void>> result = new ArrayList<>(); List<ListenableFuture<Void>> result = new ArrayList<>();
try { try {
if (uplinkMsg.getEntityDataCount() > 0) {
for (EntityDataProto entityData : uplinkMsg.getEntityDataList()) {
result.addAll(ctx.getTelemetryProcessor().processTelemetryMsg(edge.getTenantId(), entityData));
}
}
if (uplinkMsg.getDeviceProfileUpdateMsgCount() > 0) { if (uplinkMsg.getDeviceProfileUpdateMsgCount() > 0) {
for (DeviceProfileUpdateMsg deviceProfileUpdateMsg : uplinkMsg.getDeviceProfileUpdateMsgList()) { for (DeviceProfileUpdateMsg deviceProfileUpdateMsg : uplinkMsg.getDeviceProfileUpdateMsgList()) {
result.add(((DeviceProfileProcessor) ctx.getDeviceProfileEdgeProcessorFactory().getProcessorByEdgeVersion(this.edgeVersion)) result.add(((DeviceProfileProcessor) ctx.getDeviceProfileEdgeProcessorFactory().getProcessorByEdgeVersion(this.edgeVersion))
@ -731,6 +726,17 @@ public final class EdgeGrpcSession implements Closeable {
.processAssetMsgFromEdge(edge.getTenantId(), edge, assetUpdateMsg)); .processAssetMsgFromEdge(edge.getTenantId(), edge, assetUpdateMsg));
} }
} }
if (uplinkMsg.getEntityViewUpdateMsgCount() > 0) {
for (EntityViewUpdateMsg entityViewUpdateMsg : uplinkMsg.getEntityViewUpdateMsgList()) {
result.add(((EntityViewProcessor) ctx.getEntityViewProcessorFactory().getProcessorByEdgeVersion(this.edgeVersion))
.processEntityViewMsgFromEdge(edge.getTenantId(), edge, entityViewUpdateMsg));
}
}
if (uplinkMsg.getEntityDataCount() > 0) {
for (EntityDataProto entityData : uplinkMsg.getEntityDataList()) {
result.addAll(ctx.getTelemetryProcessor().processTelemetryMsg(edge.getTenantId(), entityData));
}
}
if (uplinkMsg.getAlarmUpdateMsgCount() > 0) { if (uplinkMsg.getAlarmUpdateMsgCount() > 0) {
for (AlarmUpdateMsg alarmUpdateMsg : uplinkMsg.getAlarmUpdateMsgList()) { for (AlarmUpdateMsg alarmUpdateMsg : uplinkMsg.getAlarmUpdateMsgList()) {
result.add(((AlarmProcessor) ctx.getAlarmEdgeProcessorFactory().getProcessorByEdgeVersion(this.edgeVersion)) result.add(((AlarmProcessor) ctx.getAlarmEdgeProcessorFactory().getProcessorByEdgeVersion(this.edgeVersion))
@ -743,12 +749,6 @@ public final class EdgeGrpcSession implements Closeable {
.processAlarmCommentMsgFromEdge(edge.getTenantId(), edge.getId(), alarmCommentUpdateMsg)); .processAlarmCommentMsgFromEdge(edge.getTenantId(), edge.getId(), alarmCommentUpdateMsg));
} }
} }
if (uplinkMsg.getEntityViewUpdateMsgCount() > 0) {
for (EntityViewUpdateMsg entityViewUpdateMsg : uplinkMsg.getEntityViewUpdateMsgList()) {
result.add(((EntityViewProcessor) ctx.getEntityViewProcessorFactory().getProcessorByEdgeVersion(this.edgeVersion))
.processEntityViewMsgFromEdge(edge.getTenantId(), edge, entityViewUpdateMsg));
}
}
if (uplinkMsg.getRelationUpdateMsgCount() > 0) { if (uplinkMsg.getRelationUpdateMsgCount() > 0) {
for (RelationUpdateMsg relationUpdateMsg : uplinkMsg.getRelationUpdateMsgList()) { for (RelationUpdateMsg relationUpdateMsg : uplinkMsg.getRelationUpdateMsgList()) {
result.add(((RelationProcessor) ctx.getRelationEdgeProcessorFactory().getProcessorByEdgeVersion(this.edgeVersion)) result.add(((RelationProcessor) ctx.getRelationEdgeProcessorFactory().getProcessorByEdgeVersion(this.edgeVersion))
@ -763,7 +763,8 @@ public final class EdgeGrpcSession implements Closeable {
} }
if (uplinkMsg.getResourceUpdateMsgCount() > 0) { if (uplinkMsg.getResourceUpdateMsgCount() > 0) {
for (ResourceUpdateMsg resourceUpdateMsg : uplinkMsg.getResourceUpdateMsgList()) { for (ResourceUpdateMsg resourceUpdateMsg : uplinkMsg.getResourceUpdateMsgList()) {
result.add(((ResourceProcessor) ctx.getResourceEdgeProcessorFactory().getProcessorByEdgeVersion(this.edgeVersion)).processResourceMsgFromEdge(edge.getTenantId(), edge, resourceUpdateMsg)); result.add(((ResourceProcessor) ctx.getResourceEdgeProcessorFactory().getProcessorByEdgeVersion(this.edgeVersion))
.processResourceMsgFromEdge(edge.getTenantId(), edge, resourceUpdateMsg));
} }
} }
if (uplinkMsg.getRuleChainMetadataRequestMsgCount() > 0) { if (uplinkMsg.getRuleChainMetadataRequestMsgCount() > 0) {

1
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/BaseAssetProfileProcessor.java

@ -47,7 +47,6 @@ public abstract class BaseAssetProfileProcessor extends BaseEdgeProcessor {
assetProfile.setId(assetProfileId); assetProfile.setId(assetProfileId);
assetProfile.setDefault(assetProfileById.isDefault()); assetProfile.setDefault(assetProfileById.isDefault());
} }
assetProfile.setDefault(false);
String assetProfileName = assetProfile.getName(); String assetProfileName = assetProfile.getName();
AssetProfile assetProfileByName = assetProfileService.findAssetProfileByName(tenantId, assetProfileName); AssetProfile assetProfileByName = assetProfileService.findAssetProfileByName(tenantId, assetProfileName);
if (assetProfileByName != null && !assetProfileByName.getId().equals(assetProfileId)) { if (assetProfileByName != null && !assetProfileByName.getId().equals(assetProfileId)) {

13
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/BaseDeviceProcessor.java

@ -23,7 +23,6 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg;
import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
@ -63,13 +62,6 @@ public abstract class BaseDeviceProcessor extends BaseEdgeProcessor {
device.setId(deviceId); device.setId(deviceId);
} }
Device savedDevice = deviceService.saveDevice(device, false); Device savedDevice = deviceService.saveDevice(device, false);
if (created) {
DeviceCredentials deviceCredentials = new DeviceCredentials();
deviceCredentials.setDeviceId(new DeviceId(savedDevice.getUuidId()));
deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN);
deviceCredentials.setCredentialsId(StringUtils.randomAlphanumeric(20));
deviceCredentialsService.createDeviceCredentials(device.getTenantId(), deviceCredentials);
}
tbClusterService.onDeviceUpdated(savedDevice, created ? null : device); tbClusterService.onDeviceUpdated(savedDevice, created ? null : device);
} catch (Exception e) { } catch (Exception e) {
log.error("[{}] Failed to process device update msg [{}]", tenantId, deviceUpdateMsg, e); log.error("[{}] Failed to process device update msg [{}]", tenantId, deviceUpdateMsg, e);
@ -91,6 +83,10 @@ public abstract class BaseDeviceProcessor extends BaseEdgeProcessor {
tenantId, device.getName(), deviceCredentials.getCredentialsId(), deviceCredentials.getCredentialsValue()); tenantId, device.getName(), deviceCredentials.getCredentialsId(), deviceCredentials.getCredentialsValue());
try { try {
DeviceCredentials deviceCredentialsByDeviceId = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, device.getId()); DeviceCredentials deviceCredentialsByDeviceId = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, device.getId());
if (deviceCredentialsByDeviceId == null) {
deviceCredentialsByDeviceId = new DeviceCredentials();
deviceCredentialsByDeviceId.setDeviceId(device.getId());
}
deviceCredentialsByDeviceId.setCredentialsType(deviceCredentials.getCredentialsType()); deviceCredentialsByDeviceId.setCredentialsType(deviceCredentials.getCredentialsType());
deviceCredentialsByDeviceId.setCredentialsId(deviceCredentials.getCredentialsId()); deviceCredentialsByDeviceId.setCredentialsId(deviceCredentials.getCredentialsId());
deviceCredentialsByDeviceId.setCredentialsValue(deviceCredentials.getCredentialsValue()); deviceCredentialsByDeviceId.setCredentialsValue(deviceCredentials.getCredentialsValue());
@ -111,4 +107,5 @@ public abstract class BaseDeviceProcessor extends BaseEdgeProcessor {
protected abstract void setCustomerId(TenantId tenantId, CustomerId customerId, Device device, DeviceUpdateMsg deviceUpdateMsg); protected abstract void setCustomerId(TenantId tenantId, CustomerId customerId, Device device, DeviceUpdateMsg deviceUpdateMsg);
protected abstract DeviceCredentials constructDeviceCredentialsFromUpdateMsg(TenantId tenantId, DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg); protected abstract DeviceCredentials constructDeviceCredentialsFromUpdateMsg(TenantId tenantId, DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg);
} }

21
common/actor/src/main/java/org/thingsboard/server/actors/DefaultTbActorSystem.java

@ -156,14 +156,29 @@ public class DefaultTbActorSystem implements TbActorSystem {
@Override @Override
public void broadcastToChildren(TbActorId parent, TbActorMsg msg) { public void broadcastToChildren(TbActorId parent, TbActorMsg msg) {
broadcastToChildren(parent, id -> true, msg); broadcastToChildren(parent, msg, false);
}
@Override
public void broadcastToChildren(TbActorId parent, TbActorMsg msg, boolean highPriority) {
broadcastToChildren(parent, id -> true, msg, highPriority);
} }
@Override @Override
public void broadcastToChildren(TbActorId parent, Predicate<TbActorId> childFilter, TbActorMsg msg) { public void broadcastToChildren(TbActorId parent, Predicate<TbActorId> childFilter, TbActorMsg msg) {
broadcastToChildren(parent, childFilter, msg, false);
}
private void broadcastToChildren(TbActorId parent, Predicate<TbActorId> childFilter, TbActorMsg msg, boolean highPriority) {
Set<TbActorId> children = parentChildMap.get(parent); Set<TbActorId> children = parentChildMap.get(parent);
if (children != null) { if (children != null) {
children.stream().filter(childFilter).forEach(id -> tell(id, msg)); children.stream().filter(childFilter).forEach(id -> {
try {
tell(id, msg, highPriority);
} catch (TbActorNotRegisteredException e) {
log.warn("Actor is missing for {}", id);
}
});
} }
} }
@ -190,6 +205,8 @@ public class DefaultTbActorSystem implements TbActorSystem {
stop(child); stop(child);
} }
} }
parentChildMap.values().forEach(parentChildren -> parentChildren.remove(actorId));
TbActorMailbox mailbox = actors.remove(actorId); TbActorMailbox mailbox = actors.remove(actorId);
if (mailbox != null) { if (mailbox != null) {
mailbox.destroy(null); mailbox.destroy(null);

2
common/actor/src/main/java/org/thingsboard/server/actors/TbActor.java

@ -34,7 +34,7 @@ public interface TbActor {
return InitFailureStrategy.retryWithDelay(5000L * attempt); return InitFailureStrategy.retryWithDelay(5000L * attempt);
} }
default ProcessFailureStrategy onProcessFailure(Throwable t) { default ProcessFailureStrategy onProcessFailure(TbActorMsg msg, Throwable t) {
if (t instanceof Error) { if (t instanceof Error) {
return ProcessFailureStrategy.stop(); return ProcessFailureStrategy.stop();
} else { } else {

2
common/actor/src/main/java/org/thingsboard/server/actors/TbActorCtx.java

@ -36,6 +36,8 @@ public interface TbActorCtx extends TbActorRef {
void broadcastToChildren(TbActorMsg msg); void broadcastToChildren(TbActorMsg msg);
void broadcastToChildren(TbActorMsg msg, boolean highPriority);
void broadcastToChildrenByType(TbActorMsg msg, EntityType entityType); void broadcastToChildrenByType(TbActorMsg msg, EntityType entityType);
void broadcastToChildren(TbActorMsg msg, Predicate<TbActorId> childFilter); void broadcastToChildren(TbActorMsg msg, Predicate<TbActorId> childFilter);

9
common/actor/src/main/java/org/thingsboard/server/actors/TbActorMailbox.java

@ -160,7 +160,7 @@ public final class TbActorMailbox implements TbActorCtx {
destroy(updateException.getCause()); destroy(updateException.getCause());
} catch (Throwable t) { } catch (Throwable t) {
log.debug("[{}] Failed to process message: {}", selfId, msg, t); log.debug("[{}] Failed to process message: {}", selfId, msg, t);
ProcessFailureStrategy strategy = actor.onProcessFailure(t); ProcessFailureStrategy strategy = actor.onProcessFailure(msg, t);
if (strategy.isStop()) { if (strategy.isStop()) {
system.stop(selfId); system.stop(selfId);
} }
@ -190,7 +190,12 @@ public final class TbActorMailbox implements TbActorCtx {
@Override @Override
public void broadcastToChildren(TbActorMsg msg) { public void broadcastToChildren(TbActorMsg msg) {
system.broadcastToChildren(selfId, msg); broadcastToChildren(msg, false);
}
@Override
public void broadcastToChildren(TbActorMsg msg, boolean highPriority) {
system.broadcastToChildren(selfId, msg, highPriority);
} }
@Override @Override

2
common/actor/src/main/java/org/thingsboard/server/actors/TbActorSystem.java

@ -48,6 +48,8 @@ public interface TbActorSystem {
void broadcastToChildren(TbActorId parent, TbActorMsg msg); void broadcastToChildren(TbActorId parent, TbActorMsg msg);
void broadcastToChildren(TbActorId parent, TbActorMsg msg, boolean highPriority);
void broadcastToChildren(TbActorId parent, Predicate<TbActorId> childFilter, TbActorMsg msg); void broadcastToChildren(TbActorId parent, Predicate<TbActorId> childFilter, TbActorMsg msg);
List<TbActorId> filterChildren(TbActorId parent, Predicate<TbActorId> childFilter); List<TbActorId> filterChildren(TbActorId parent, Predicate<TbActorId> childFilter);

29
common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java

@ -35,7 +35,6 @@ import java.time.format.DateTimeParseException;
import java.time.format.FormatStyle; import java.time.format.FormatStyle;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAccessor;
import java.util.Arrays;
import java.util.Locale; import java.util.Locale;
import java.util.function.BiFunction; import java.util.function.BiFunction;
@ -73,21 +72,37 @@ public class TbDate implements Serializable, Cloneable {
instant = Instant.ofEpochMilli(dateMilliSecond); instant = Instant.ofEpochMilli(dateMilliSecond);
} }
public TbDate(int year, int month, int date, String... tz) { public TbDate(int year, int month, int date) {
this(year, month, date, 0, 0, 0, 0, null);
}
public TbDate(int year, int month, int date, String tz) {
this(year, month, date, 0, 0, 0, 0, tz); this(year, month, date, 0, 0, 0, 0, tz);
} }
public TbDate(int year, int month, int date, int hrs, int min, String... tz) { public TbDate(int year, int month, int date, int hrs, int min) {
this(year, month, date, hrs, min, 0, 0, null);
}
public TbDate(int year, int month, int date, int hrs, int min, String tz) {
this(year, month, date, hrs, min, 0, 0, tz); this(year, month, date, hrs, min, 0, 0, tz);
} }
public TbDate(int year, int month, int date, int hrs, int min, int second, String... tz) { public TbDate(int year, int month, int date, int hrs, int min, int second) {
this(year, month, date, hrs, min, second, 0, null);
}
public TbDate(int year, int month, int date, int hrs, int min, int second, String tz) {
this(year, month, date, hrs, min, second, 0, tz); this(year, month, date, hrs, min, second, 0, tz);
} }
public TbDate(int year, int month, int date, int hrs, int min, int second, int secondMilli, String... tz) { public TbDate(int year, int month, int date, int hrs, int min, int second, int milliSecond) {
ZoneId zoneId = tz.length > 0 ? ZoneId.of(Arrays.stream(tz).findFirst().get()) : ZoneId.systemDefault(); this(year, month, date, hrs, min, second, milliSecond, null);
instant = parseInstant(year, month, date, hrs, min, second, secondMilli, zoneId); }
public TbDate(int year, int month, int date, int hrs, int min, int second, int milliSecond, String tz) {
ZoneId zoneId = tz != null && tz.length() > 0 ? ZoneId.of(tz) : ZoneId.systemDefault();
instant = parseInstant(year, month, date, hrs, min, second, milliSecond, zoneId);
} }
public Instant getInstant() { public Instant getInstant() {

1
dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java

@ -163,7 +163,6 @@ public class DeviceServiceImpl extends AbstractCachedEntityService<DeviceCacheKe
return doSaveDevice(device, accessToken, true); return doSaveDevice(device, accessToken, true);
} }
@Transactional
@Override @Override
public Device saveDevice(Device device, boolean doValidate) { public Device saveDevice(Device device, boolean doValidate) {
return doSaveDevice(device, null, doValidate); return doSaveDevice(device, null, doValidate);

20
dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceTest.java

@ -16,7 +16,6 @@
package org.thingsboard.server.dao.service; package org.thingsboard.server.dao.service;
import com.datastax.oss.driver.api.core.uuid.Uuids; import com.datastax.oss.driver.api.core.uuid.Uuids;
import org.hibernate.exception.ConstraintViolationException;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
@ -57,7 +56,6 @@ import org.thingsboard.server.dao.service.validator.DeviceCredentialsDataValidat
import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantProfileService;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -160,24 +158,6 @@ public class DeviceServiceTest extends AbstractServiceTest {
Assertions.assertNull(deviceByName); Assertions.assertNull(deviceByName);
} }
@Test
public void testShouldRollbackNotValidatedDeviceIfDeviceCredentialsValidationFailed() {
Mockito.reset(validator);
Mockito.doThrow(new DataValidationException("mock message"))
.when(validator).validate(any(), any());
Device device = new Device();
device.setTenantId(tenantId);
device.setName(StringUtils.randomAlphabetic(10));
device.setType("default");
assertThatThrownBy(() -> deviceService.saveDevice(device, false))
.isInstanceOf(DataValidationException.class)
.hasMessageContaining("mock message");
Device deviceByName = deviceService.findDeviceByTenantIdAndName(tenantId, device.getName());
Assertions.assertNull(deviceByName);
}
@Test @Test
public void testShouldRollbackValidatedDeviceIfDeviceCredentialsValidationFailed() { public void testShouldRollbackValidatedDeviceIfDeviceCredentialsValidationFailed() {
Mockito.reset(validator); Mockito.reset(validator);

2
msa/js-executor/api/jsInvokeMessageProcessor.ts

@ -169,7 +169,7 @@ export class JsInvokeMessageProcessor {
this.logger.info('[%s] Executing script body: [%s]', scriptId, invokeRequest.scriptBody); this.logger.info('[%s] Executing script body: [%s]', scriptId, invokeRequest.scriptBody);
} }
if (this.executedScriptsCounter % memoryUsageTraceFrequency == 0) { if (this.executedScriptsCounter % memoryUsageTraceFrequency == 0) {
this.logger.info('Current memory usage: [%s]', process.memoryUsage()); this.logger.info('Current memory usage: %s', JSON.stringify(process.memoryUsage()));
} }
this.getOrCompileScript(scriptId, invokeRequest.scriptBody).then( this.getOrCompileScript(scriptId, invokeRequest.scriptBody).then(

4
ui-ngx/src/app/core/http/public-api.ts

@ -16,7 +16,9 @@
export * from './admin.service'; export * from './admin.service';
export * from './alarm.service'; export * from './alarm.service';
export * from './alarm-comment.service';
export * from './asset.service'; export * from './asset.service';
export * from './asset-profile.service';
export * from './attribute.service'; export * from './attribute.service';
export * from './audit-log.service'; export * from './audit-log.service';
export * from './component-descriptor.service'; export * from './component-descriptor.service';
@ -24,12 +26,14 @@ export * from './customer.service';
export * from './dashboard.service'; export * from './dashboard.service';
export * from './device.service'; export * from './device.service';
export * from './device-profile.service'; export * from './device-profile.service';
export * from './entities-version-control.service';
export * from './entity.service'; export * from './entity.service';
export * from './edge.service'; export * from './edge.service';
export * from './entity-relation.service'; export * from './entity-relation.service';
export * from './entity-view.service'; export * from './entity-view.service';
export * from './event.service'; export * from './event.service';
export * from './http-utils'; export * from './http-utils';
export * from './image.service';
export * from './notification.service'; export * from './notification.service';
export * from './oauth2.service'; export * from './oauth2.service';
export * from './ota-package.service'; export * from './ota-package.service';

10
ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.ts

@ -45,6 +45,7 @@ import { deepClone } from '@core/utils';
import { EntityType } from '@shared/models/entity-type.models'; import { EntityType } from '@shared/models/entity-type.models';
import { fromEvent, Subscription } from 'rxjs'; import { fromEvent, Subscription } from 'rxjs';
import { POSITION_MAP } from '@shared/models/overlay.models'; import { POSITION_MAP } from '@shared/models/overlay.models';
import { UtilsService } from '@core/services/utils.service';
export const ALARM_FILTER_CONFIG_DATA = new InjectionToken<any>('AlarmFilterConfigData'); export const ALARM_FILTER_CONFIG_DATA = new InjectionToken<any>('AlarmFilterConfigData');
@ -127,7 +128,8 @@ export class AlarmFilterConfigComponent implements OnInit, OnDestroy, ControlVal
private translate: TranslateService, private translate: TranslateService,
private overlay: Overlay, private overlay: Overlay,
private nativeElement: ElementRef, private nativeElement: ElementRef,
private viewContainerRef: ViewContainerRef) { private viewContainerRef: ViewContainerRef,
private utils: UtilsService) {
} }
ngOnInit(): void { ngOnInit(): void {
@ -298,7 +300,7 @@ export class AlarmFilterConfigComponent implements OnInit, OnDestroy, ControlVal
this.translate.instant(alarmSeverityTranslations.get(s))).join(', ')); this.translate.instant(alarmSeverityTranslations.get(s))).join(', '));
} }
if (this.alarmFilterConfig?.typeList?.length) { if (this.alarmFilterConfig?.typeList?.length) {
filterTextParts.push(this.alarmFilterConfig.typeList.join(', ')); filterTextParts.push(this.alarmFilterConfig.typeList.map((type) => this.customTranslate(type)).join(', '));
} }
if (this.alarmFilterConfig?.assignedToCurrentUser) { if (this.alarmFilterConfig?.assignedToCurrentUser) {
filterTextParts.push(this.translate.instant('alarm.assigned-to-me')); filterTextParts.push(this.translate.instant('alarm.assigned-to-me'));
@ -313,4 +315,8 @@ export class AlarmFilterConfigComponent implements OnInit, OnDestroy, ControlVal
} }
} }
private customTranslate(entity: string) {
return this.utils.customTranslation(entity, entity);
}
} }

1
ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html

@ -73,7 +73,6 @@
<mat-icon matChipRemove>close</mat-icon> <mat-icon matChipRemove>close</mat-icon>
</mat-chip-row> </mat-chip-row>
<input matInput type="text" placeholder="{{'device-profile.alarm-rule-relation-types-list' | translate}}" <input matInput type="text" placeholder="{{'device-profile.alarm-rule-relation-types-list' | translate}}"
style="max-width: 200px;"
[matChipInputFor]="relationTypesChipList" [matChipInputFor]="relationTypesChipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes" [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
matChipInputAddOnBlur matChipInputAddOnBlur

4
ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.component.ts

@ -157,7 +157,6 @@ export class SignalStrengthWidgetComponent implements OnInit, OnDestroy, AfterVi
ngOnInit(): void { ngOnInit(): void {
this.ctx.$scope.signalStrengthWidget = this; this.ctx.$scope.signalStrengthWidget = this;
this.settings = {...signalStrengthDefaultSettings, ...this.ctx.settings}; this.settings = {...signalStrengthDefaultSettings, ...this.ctx.settings};
this.layout = this.settings.layout; this.layout = this.settings.layout;
this.showDate = this.settings.showDate; this.showDate = this.settings.showDate;
@ -262,7 +261,7 @@ export class SignalStrengthWidgetComponent implements OnInit, OnDestroy, AfterVi
} }
} }
this.noSignal = this.rssi <= -100; this.noSignal = this.rssi <= this.settings.noSignalRssiValue;
this.activeBarsColor.update(this.rssi); this.activeBarsColor.update(this.rssi);
@ -404,5 +403,4 @@ export class SignalStrengthWidgetComponent implements OnInit, OnDestroy, AfterVi
this.renderer.setStyle(this.signalStrengthTooltip.nativeElement, 'transform', `scale(${scale})`); this.renderer.setStyle(this.signalStrengthTooltip.nativeElement, 'transform', `scale(${scale})`);
} }
} }
} }

2
ui-ngx/src/app/modules/home/components/widget/lib/indicator/signal-strength-widget.models.ts

@ -53,6 +53,7 @@ export interface SignalStrengthWidgetSettings {
dateFont: Font; dateFont: Font;
dateColor: string; dateColor: string;
activeBarsColor: ColorSettings; activeBarsColor: ColorSettings;
noSignalRssiValue: number;
inactiveBarsColor: string; inactiveBarsColor: string;
showTooltip: boolean; showTooltip: boolean;
showTooltipValue: boolean; showTooltipValue: boolean;
@ -91,6 +92,7 @@ export const signalStrengthDefaultSettings: SignalStrengthWidgetSettings = {
], ],
colorFunction: defaultColorFunction colorFunction: defaultColorFunction
}, },
noSignalRssiValue: -100,
inactiveBarsColor: 'rgba(224, 224, 224, 1)', inactiveBarsColor: 'rgba(224, 224, 224, 1)',
showTooltip: true, showTooltip: true,
showTooltipValue: true, showTooltipValue: true,

6
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.html

@ -45,8 +45,10 @@
widgetActionFormGroup.get('type').value === widgetActionType.openDashboard ? widgetActionFormGroup.get('type').value === widgetActionType.openDashboard ?
widgetActionFormGroup.get('type').value : ''"> widgetActionFormGroup.get('type').value : ''">
<div class="tb-form-row"> <div class="tb-form-row">
<div class="fixed-title-width">{{ 'widget-action.target-dashboard-state' | translate }} <div class="fixed-title-width">
{{widgetActionFormGroup.get('type').value === widgetActionType.openDashboardState ? '*' : ''}}</div> <span>{{ 'widget-action.target-dashboard-state' | translate }}</span>
<span *ngIf="widgetActionFormGroup.get('type').value === widgetActionType.openDashboardState">*</span>
</div>
<mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic" <mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic"
[class.tb-suffix-absolute]="!actionTypeFormGroup.get('targetDashboardStateId').value"> [class.tb-suffix-absolute]="!actionTypeFormGroup.get('targetDashboardStateId').value">
<input matInput type="text" placeholder="{{ 'widget-action.target-dashboard-state' | translate }}" <input matInput type="text" placeholder="{{ 'widget-action.target-dashboard-state' | translate }}"

18
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.ts

@ -34,7 +34,7 @@ import {
import { WidgetService } from '@core/http/widget.service'; import { WidgetService } from '@core/http/widget.service';
import { WidgetActionCallbacks } from '@home/components/widget/action/manage-widget-actions.component.models'; import { WidgetActionCallbacks } from '@home/components/widget/action/manage-widget-actions.component.models';
import { map, mergeMap, share, startWith, takeUntil, tap } from 'rxjs/operators'; import { map, mergeMap, share, startWith, takeUntil, tap } from 'rxjs/operators';
import { Observable, of, Subject, Subscription } from 'rxjs'; import { Observable, of, ReplaySubject, Subject, Subscription } from 'rxjs';
import { Dashboard } from '@shared/models/dashboard.models'; import { Dashboard } from '@shared/models/dashboard.models';
import { DashboardService } from '@core/http/dashboard.service'; import { DashboardService } from '@core/http/dashboard.service';
import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
@ -228,16 +228,16 @@ export class WidgetActionComponent implements ControlValueAccessor, OnInit, Vali
this.fb.control(action ? action.stateEntityParamName : null, []) this.fb.control(action ? action.stateEntityParamName : null, [])
); );
if (type === WidgetActionType.openDashboard) { if (type === WidgetActionType.openDashboard) {
const targetDashboardId = action ? action.targetDashboardId : null;
this.actionTypeFormGroup.addControl( this.actionTypeFormGroup.addControl(
'openNewBrowserTab', 'openNewBrowserTab',
this.fb.control(action ? action.openNewBrowserTab : false, []) this.fb.control(action ? action.openNewBrowserTab : false, [])
); );
this.actionTypeFormGroup.addControl( this.actionTypeFormGroup.addControl(
'targetDashboardId', 'targetDashboardId',
this.fb.control(action ? action.targetDashboardId : null, this.fb.control(targetDashboardId, [Validators.required])
[Validators.required])
); );
this.setupSelectedDashboardStateIds(); this.setupSelectedDashboardStateIds(targetDashboardId);
} else { } else {
if (type === WidgetActionType.openDashboardState) { if (type === WidgetActionType.openDashboardState) {
const displayType = this.getStateDisplayType(action); const displayType = this.getStateDisplayType(action);
@ -367,9 +367,10 @@ export class WidgetActionComponent implements ControlValueAccessor, OnInit, Vali
); );
} }
private setupSelectedDashboardStateIds() { private setupSelectedDashboardStateIds(targetDashboardId: string | null) {
this.selectedDashboardStateIds = this.selectedDashboardStateIds =
this.actionTypeFormGroup.get('targetDashboardId').valueChanges.pipe( this.actionTypeFormGroup.get('targetDashboardId').valueChanges.pipe(
startWith(targetDashboardId),
tap((dashboardId) => { tap((dashboardId) => {
if (!dashboardId) { if (!dashboardId) {
this.actionTypeFormGroup.get('targetDashboardStateId') this.actionTypeFormGroup.get('targetDashboardStateId')
@ -400,7 +401,12 @@ export class WidgetActionComponent implements ControlValueAccessor, OnInit, Vali
return []; return [];
} }
}), }),
share() share({
connector: () => new ReplaySubject(1),
resetOnError: false,
resetOnComplete: false,
resetOnRefCountZero: false
})
); );
} }

87
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.html

@ -17,49 +17,54 @@
--> -->
<div class="tb-background-settings-panel" [formGroup]="backgroundSettingsFormGroup"> <div class="tb-background-settings-panel" [formGroup]="backgroundSettingsFormGroup">
<div class="tb-background-settings-title" translate>widgets.background.background-settings</div> <div class="tb-background-settings-title" translate>widgets.background.background-settings</div>
<div class="tb-form-panel tb-background-form-panel"> <div class="tb-background-settings-panel-content">
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px"> <div class="tb-form-panel tb-background-form-panel">
<div class="tb-form-panel-title" translate>widgets.background.background</div> <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<tb-toggle-select formControlName="type" fxFlex selectMediaBreakpoint="xs"> <div class="tb-form-panel-title" translate>widgets.background.background</div>
<tb-toggle-option *ngFor="let type of backgroundTypes" <tb-toggle-select formControlName="type" fxFlex selectMediaBreakpoint="xs">
[value]="type"> <tb-toggle-option *ngFor="let type of backgroundTypes"
{{ backgroundTypeTranslationsMap.get(type) | translate }} [value]="type">
</tb-toggle-option> {{ backgroundTypeTranslationsMap.get(type) | translate }}
</tb-toggle-select> </tb-toggle-option>
</div> </tb-toggle-select>
<tb-gallery-image-input [fxShow]="backgroundSettingsFormGroup.get('type').value === backgroundType.image" formControlName="imageUrl"></tb-gallery-image-input> </div>
<div [fxShow]="backgroundSettingsFormGroup.get('type').value === backgroundType.color" class="tb-form-row space-between tb-background-color-field"> <tb-gallery-image-input [fxShow]="backgroundSettingsFormGroup.get('type').value === backgroundType.image"
<div translate>widgets.color.color</div> formControlName="imageUrl"></tb-gallery-image-input>
<tb-color-input asBoxInput <div [fxShow]="backgroundSettingsFormGroup.get('type').value === backgroundType.color"
formControlName="color"> class="tb-form-row space-between tb-background-color-field">
</tb-color-input> <div translate>widgets.color.color</div>
</div> <tb-color-input asBoxInput
</div> formControlName="color">
<div class="tb-form-panel" formGroupName="overlay"> </tb-color-input>
<div class="tb-form-panel-title" translate>widgets.background.overlay</div> </div>
<mat-slide-toggle class="mat-slide" formControlName="enabled">
{{ 'widgets.background.enable-overlay' | translate }}
</mat-slide-toggle>
<div class="tb-form-row space-between">
<div translate>widgets.color.color</div>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>
</div>
<div class="tb-form-row space-between">
<div translate>widgets.background.blur</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="blur" type="number" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}">
<div matSuffix>px</div>
</mat-form-field>
</div> </div>
</div> <div class="tb-form-panel" formGroupName="overlay">
<div class="tb-background-settings-preview"> <div class="tb-form-panel-title" translate>widgets.background.overlay</div>
<div class="tb-background-settings-preview-title" translate> <mat-slide-toggle class="mat-slide" formControlName="enabled">
widgets.background.preview {{ 'widgets.background.enable-overlay' | translate }}
</mat-slide-toggle>
<div class="tb-form-row space-between">
<div translate>widgets.color.color</div>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>
</div>
<div class="tb-form-row space-between">
<div translate>widgets.background.blur</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="blur" type="number" min="0" step="1"
placeholder="{{ 'widget-config.set' | translate }}">
<div matSuffix>px</div>
</mat-form-field>
</div>
</div> </div>
<div class="tb-background-settings-preview-box mat-elevation-z4" [style]="backgroundStyle$ | async"> <div class="tb-background-settings-preview">
<div class="tb-background-settings-preview-overlay" [style]="overlayStyle"> <div class="tb-background-settings-preview-title" translate>
widgets.background.preview
</div>
<div class="tb-background-settings-preview-box mat-elevation-z4" [style]="backgroundStyle$ | async">
<div class="tb-background-settings-preview-overlay" [style]="overlayStyle">
</div>
</div> </div>
</div> </div>
</div> </div>

8
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.scss

@ -67,6 +67,14 @@
left: 7.998px; left: 7.998px;
right: 7.998px; right: 7.998px;
} }
.tb-background-settings-panel-content {
display: flex;
flex-direction: column;
gap: 16px;
overflow: auto;
margin: -10px;
padding: 10px;
}
.tb-background-settings-panel-buttons { .tb-background-settings-panel-buttons {
height: 40px; height: 40px;
display: flex; display: flex;

2
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.ts

@ -110,7 +110,7 @@ export class BackgroundSettingsComponent implements OnInit, ControlValueAccessor
backgroundSettings: this.modelValue backgroundSettings: this.modelValue
}; };
const backgroundSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, const backgroundSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, BackgroundSettingsPanelComponent, 'left', true, null, this.viewContainerRef, BackgroundSettingsPanelComponent, ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], true, null,
ctx, ctx,
{}, {},
{}, {}, true); {}, {}, true);

7
ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/signal-strength-widget-settings.component.html

@ -62,6 +62,13 @@
<tb-background-settings formControlName="background"> <tb-background-settings formControlName="background">
</tb-background-settings> </tb-background-settings>
</div> </div>
<div class="tb-form-row space-between">
<div translate>widgets.signal-strength.no-signal-rssi-value</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="noSignalRssiValue" type="number" step="1"
placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
</div> </div>
<div class="tb-form-panel tb-slide-toggle"> <div class="tb-form-panel tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="signalStrengthWidgetSettingsForm.get('showTooltip').value" <mat-expansion-panel class="tb-settings" [expanded]="signalStrengthWidgetSettingsForm.get('showTooltip').value"

3
ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/signal-strength-widget-settings.component.ts

@ -86,7 +86,8 @@ export class SignalStrengthWidgetSettingsComponent extends WidgetSettingsCompone
tooltipBackgroundColor: [settings.tooltipBackgroundColor, []], tooltipBackgroundColor: [settings.tooltipBackgroundColor, []],
tooltipBackgroundBlur: [settings.tooltipBackgroundBlur, []], tooltipBackgroundBlur: [settings.tooltipBackgroundBlur, []],
background: [settings.background, []] background: [settings.background, []],
noSignalRssiValue: [settings.noSignalRssiValue, []]
}); });
} }

12
ui-ngx/src/app/modules/home/models/services.map.ts

@ -44,12 +44,19 @@ import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service'
import { NotificationService } from '@core/http/notification.service'; import { NotificationService } from '@core/http/notification.service';
import { MillisecondsToTimeStringPipe } from '@shared/pipe/milliseconds-to-time-string.pipe'; import { MillisecondsToTimeStringPipe } from '@shared/pipe/milliseconds-to-time-string.pipe';
import { UserSettingsService } from '@core/http/user-settings.service'; import { UserSettingsService } from '@core/http/user-settings.service';
import { ImageService } from '@core/http/image.service';
import { AlarmCommentService } from '@core/http/alarm-comment.service';
import { TenantService } from '@core/http/tenant.service';
import { TenantProfileService } from '@core/http/tenant-profile.service';
import { UiSettingsService } from '@core/http/ui-settings.service';
import { UsageInfoService } from '@core/http/usage-info.service';
export const ServicesMap = new Map<string, Type<any>>( export const ServicesMap = new Map<string, Type<any>>(
[ [
['broadcastService', BroadcastService], ['broadcastService', BroadcastService],
['deviceService', DeviceService], ['deviceService', DeviceService],
['alarmService', AlarmService], ['alarmService', AlarmService],
['alarmCommentService', AlarmCommentService],
['assetService', AssetService], ['assetService', AssetService],
['entityViewService', EntityViewService], ['entityViewService', EntityViewService],
['customerService', CustomerService], ['customerService', CustomerService],
@ -66,6 +73,7 @@ export const ServicesMap = new Map<string, Type<any>>(
['translate', TranslateService], ['translate', TranslateService],
['http', HttpClient], ['http', HttpClient],
['router', Router], ['router', Router],
['imageService', ImageService],
['importExport', ImportExportService], ['importExport', ImportExportService],
['deviceProfileService', DeviceProfileService], ['deviceProfileService', DeviceProfileService],
['assetProfileService', AssetProfileService], ['assetProfileService', AssetProfileService],
@ -74,7 +82,11 @@ export const ServicesMap = new Map<string, Type<any>>(
['resourceService', ResourceService], ['resourceService', ResourceService],
['twoFactorAuthenticationService', TwoFactorAuthenticationService], ['twoFactorAuthenticationService', TwoFactorAuthenticationService],
['telemetryWsService', TelemetryWebsocketService], ['telemetryWsService', TelemetryWebsocketService],
['tenantService', TenantService],
['tenantProfileService', TenantProfileService],
['userSettingsService', UserSettingsService], ['userSettingsService', UserSettingsService],
['uiSettingsService', UiSettingsService],
['usageInfoService', UsageInfoService],
['notificationService', NotificationService] ['notificationService', NotificationService]
] ]
); );

3
ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html

@ -70,6 +70,8 @@
formControlName="targets" formControlName="targets"
subType="{{ ruleNotificationForm.get('triggerType').value }}" subType="{{ ruleNotificationForm.get('triggerType').value }}"
entityType="{{ entityType.NOTIFICATION_TARGET }}" entityType="{{ entityType.NOTIFICATION_TARGET }}"
labelText="{{ 'notification.recipients' | translate }}"
requiredText="{{ 'notification.recipients-required' | translate }}"
placeholderText="{{ 'notification.recipient' | translate }}"> placeholderText="{{ 'notification.recipient' | translate }}">
<button #createTargetButton <button #createTargetButton
mat-button color="primary" matSuffix mat-button color="primary" matSuffix
@ -453,6 +455,7 @@
<fieldset class="fields-group tb-margin-before-field"> <fieldset class="fields-group tb-margin-before-field">
<legend translate>notification.filter</legend> <legend translate>notification.filter</legend>
<tb-entity-list labelText="{{'edge.edge-instances' | translate}}" <tb-entity-list labelText="{{'edge.edge-instances' | translate}}"
hint="{{ 'notification.edge-list-rule-hint' | translate }}"
[entityType]="entityType.EDGE" [entityType]="entityType.EDGE"
formControlName="edges"> formControlName="edges">
</tb-entity-list> </tb-entity-list>

28
ui-ngx/src/app/modules/home/pages/notification/sent/sent-notification-dialog.component.html

@ -36,7 +36,7 @@
<mat-icon>check</mat-icon> <mat-icon>check</mat-icon>
</ng-template> </ng-template>
<mat-step [stepControl]="notificationRequestForm"> <mat-step [stepControl]="notificationRequestForm">
<ng-template matStepLabel>{{ 'notification.compose' | translate }}</ng-template> <ng-template matStepLabel>{{ 'notification.setup' | translate }}</ng-template>
<form [formGroup]="notificationRequestForm"> <form [formGroup]="notificationRequestForm">
<div fxLayoutAlign="center"> <div fxLayoutAlign="center">
<tb-toggle-select formControlName="useTemplate" appearance="fill"> <tb-toggle-select formControlName="useTemplate" appearance="fill">
@ -140,20 +140,24 @@
<mat-step *ngIf="!notificationRequestForm.get('useTemplate').value" <mat-step *ngIf="!notificationRequestForm.get('useTemplate').value"
[stepControl]="notificationTemplateConfigurationForm" #composeStep=matStep> [stepControl]="notificationTemplateConfigurationForm" #composeStep=matStep>
<ng-template matStepLabel>{{ 'notification.compose' | translate }}</ng-template> <ng-template matStepLabel>{{ 'notification.compose' | translate }}</ng-template>
<form [formGroup]="notificationTemplateConfigurationForm"> <ng-template matStepContent>
<tb-template-configuration <form [formGroup]="notificationTemplateConfigurationForm">
[notificationType]="templateNotificationForm.get('notificationType').value" <tb-template-configuration
[predefinedDeliveryMethodsTemplate]="deliveryConfiguration" notificationType="{{ notificationType.GENERAL }}"
[interacted]="composeStep.interacted" [predefinedDeliveryMethodsTemplate]="deliveryConfiguration"
formControlName="deliveryMethodsTemplates"> [interacted]="composeStep.interacted"
</tb-template-configuration> formControlName="deliveryMethodsTemplates">
</form> </tb-template-configuration>
</form>
</ng-template>
</mat-step> </mat-step>
<mat-step> <mat-step>
<ng-template matStepLabel>{{ 'notification.review' | translate }}</ng-template> <ng-template matStepLabel>{{ 'notification.review' | translate }}</ng-template>
<mat-progress-spinner color="warn" mode="indeterminate" <div class="tb-loader" *ngIf="(isLoading$ | async) && !preview">
strokeWidth="5" *ngIf="(isLoading$ | async) && !preview"> <mat-progress-spinner color="warn" mode="indeterminate"
</mat-progress-spinner> strokeWidth="5">
</mat-progress-spinner>
</div>
<div *ngIf="preview" class="tb-form-panel no-padding no-border"> <div *ngIf="preview" class="tb-form-panel no-padding no-border">
<section class="preview-group tb-form-panel stroked no-gap"> <section class="preview-group tb-form-panel stroked no-gap">
<div fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center"> <div fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">

9
ui-ngx/src/app/modules/home/pages/notification/sent/sent-notification-dialog.component.scss

@ -110,6 +110,15 @@
margin-bottom: 16px; margin-bottom: 16px;
} }
.tb-loader {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 400px;
max-height: 100%;
}
.preview-group { .preview-group {
&.notification { &.notification {
background-color: #F3F6FA; background-color: #F3F6FA;

2
ui-ngx/src/app/modules/home/pages/notification/sent/sent-notification-dialog.componet.ts

@ -147,6 +147,7 @@ export class SentNotificationDialogComponent extends
this.notificationRequestForm.reset({}, {emitEvent: false}); this.notificationRequestForm.reset({}, {emitEvent: false});
this.notificationRequestForm.patchValue(this.data.request, {emitEvent: false}); this.notificationRequestForm.patchValue(this.data.request, {emitEvent: false});
this.notificationRequestForm.get('template.name').setValue(guid()); this.notificationRequestForm.get('template.name').setValue(guid());
this.notificationRequestForm.get('template.notificationType').setValue(NotificationType.GENERAL);
let useTemplate = true; let useTemplate = true;
if (isDefinedAndNotNull(this.data.request.template)) { if (isDefinedAndNotNull(this.data.request.template)) {
useTemplate = false; useTemplate = false;
@ -155,6 +156,7 @@ export class SentNotificationDialogComponent extends
}, {emitEvent: false}); }, {emitEvent: false});
} }
this.notificationRequestForm.get('useTemplate').setValue(useTemplate, {onlySelf : true}); this.notificationRequestForm.get('useTemplate').setValue(useTemplate, {onlySelf : true});
this.deliveryConfiguration = this.templateNotificationForm.get('configuration.deliveryMethodsTemplates').value;
} }
this.refreshAllowDeliveryMethod(); this.refreshAllowDeliveryMethod();
} }

2
ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.scss

@ -17,7 +17,7 @@
.tb-template-header { .tb-template-header {
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 1; z-index: 5;
padding-bottom: 12px; padding-bottom: 12px;
background-color: var(--mdc-dialog-container-color, white); background-color: var(--mdc-dialog-container-color, white);

18
ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.html

@ -71,14 +71,16 @@
</mat-step> </mat-step>
<mat-step [stepControl]="notificationTemplateConfigurationForm" #composeStep=matStep> <mat-step [stepControl]="notificationTemplateConfigurationForm" #composeStep=matStep>
<ng-template matStepLabel>{{ 'notification.compose' | translate }}</ng-template> <ng-template matStepLabel>{{ 'notification.compose' | translate }}</ng-template>
<form [formGroup]="notificationTemplateConfigurationForm"> <ng-template matStepContent>
<tb-template-configuration <form [formGroup]="notificationTemplateConfigurationForm">
[notificationType]="templateNotificationForm.get('notificationType').value" <tb-template-configuration
[predefinedDeliveryMethodsTemplate]="deliveryConfiguration" [notificationType]="templateNotificationForm.get('notificationType').value"
[interacted]="composeStep.interacted" [predefinedDeliveryMethodsTemplate]="deliveryConfiguration"
formControlName="deliveryMethodsTemplates"> [interacted]="composeStep.interacted"
</tb-template-configuration> formControlName="deliveryMethodsTemplates">
</form> </tb-template-configuration>
</form>
</ng-template>
</mat-step> </mat-step>
</mat-horizontal-stepper> </mat-horizontal-stepper>
</div> </div>

1
ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.ts

@ -102,6 +102,7 @@ export class TemplateNotificationDialogComponent
this.notificationTemplateConfigurationForm.patchValue({ this.notificationTemplateConfigurationForm.patchValue({
deliveryMethodsTemplates: this.templateNotification.configuration.deliveryMethodsTemplates deliveryMethodsTemplates: this.templateNotification.configuration.deliveryMethodsTemplates
}, {emitEvent: false}); }, {emitEvent: false});
this.deliveryConfiguration = this.templateNotificationForm.get('configuration.deliveryMethodsTemplates').value;
} }
} }

2
ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.html

@ -31,9 +31,9 @@
style="max-width: 200px;" style="max-width: 200px;"
#entitySubtypeInput #entitySubtypeInput
(focusin)="onFocus()" (focusin)="onFocus()"
(blur)="addOnBlur($event)"
formControlName="entitySubtype" formControlName="entitySubtype"
matAutocompleteOrigin matAutocompleteOrigin
matChipInputAddOnBlur
#origin="matAutocompleteOrigin" #origin="matAutocompleteOrigin"
[matAutocompleteConnectedTo]="origin" [matAutocompleteConnectedTo]="origin"
[matAutocomplete]="entitySubtypeAutocomplete" [matAutocomplete]="entitySubtypeAutocomplete"

9
ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.ts

@ -262,6 +262,15 @@ export class EntitySubTypeListComponent implements ControlValueAccessor, OnInit,
this.clear(''); this.clear('');
} }
} }
addOnBlur(event: FocusEvent) {
if (!event.relatedTarget) {
return;
}
const value = this.entitySubtypeInput.nativeElement.value;
this.chipAdd({value} as MatChipInputEvent);
}
remove(entitySubtype: string) { remove(entitySubtype: string) {
const index = this.entitySubtypeList.indexOf(entitySubtype); const index = this.entitySubtypeList.indexOf(entitySubtype);
if (index >= 0) { if (index >= 0) {

19
ui-ngx/src/app/shared/components/image/image-dialog.component.ts

@ -14,8 +14,7 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; import { Component, Inject, OnInit } from '@angular/core';
import { ErrorStateMatcher } from '@angular/material/core';
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
@ -23,7 +22,7 @@ import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms
import { DialogComponent } from '@shared/components/dialog.component'; import { DialogComponent } from '@shared/components/dialog.component';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { ImageService } from '@core/http/image.service'; import { ImageService } from '@core/http/image.service';
import { ImageResourceInfo, imageResourceType } from '@shared/models/resource.models'; import { ImageResource, ImageResourceInfo, imageResourceType } from '@shared/models/resource.models';
import { import {
UploadImageDialogComponent, UploadImageDialogComponent,
UploadImageDialogData UploadImageDialogData
@ -143,7 +142,7 @@ export class ImageDialogComponent extends
$event.stopPropagation(); $event.stopPropagation();
} }
this.dialog.open<UploadImageDialogComponent, UploadImageDialogData, this.dialog.open<UploadImageDialogComponent, UploadImageDialogData,
ImageResourceInfo>(UploadImageDialogComponent, { ImageResource>(UploadImageDialogComponent, {
disableClose: true, disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: { data: {
@ -153,9 +152,15 @@ export class ImageDialogComponent extends
if (result) { if (result) {
this.imageChanged = true; this.imageChanged = true;
this.image = result; this.image = result;
this.imagePreviewData = { let url;
url: this.image.public ? `${this.image.publicLink}?ts=${new Date().getTime()}` : this.image.link if (result.base64) {
}; url = result.base64;
} else if (this.image.public) {
url = `${this.image.publicLink}?ts=${new Date().getTime()}`;
} else {
url = this.image.link;
}
this.imagePreviewData = {url};
} }
}); });
} }

30
ui-ngx/src/app/shared/components/image/upload-image-dialog.component.ts

@ -30,8 +30,10 @@ import {
import { DialogComponent } from '@shared/components/dialog.component'; import { DialogComponent } from '@shared/components/dialog.component';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { ImageService } from '@core/http/image.service'; import { ImageService } from '@core/http/image.service';
import { ImageResourceInfo, imageResourceType } from '@shared/models/resource.models'; import { ImageResource, ImageResourceInfo, imageResourceType } from '@shared/models/resource.models';
import { getCurrentAuthState } from '@core/auth/auth.selectors'; import { getCurrentAuthState } from '@core/auth/auth.selectors';
import { forkJoin } from 'rxjs';
import { blobToBase64 } from '@core/utils';
export interface UploadImageDialogData { export interface UploadImageDialogData {
image?: ImageResourceInfo; image?: ImageResourceInfo;
@ -44,7 +46,7 @@ export interface UploadImageDialogData {
styleUrls: [] styleUrls: []
}) })
export class UploadImageDialogComponent extends export class UploadImageDialogComponent extends
DialogComponent<UploadImageDialogComponent, ImageResourceInfo> implements OnInit, ErrorStateMatcher { DialogComponent<UploadImageDialogComponent, ImageResource> implements OnInit, ErrorStateMatcher {
uploadImageFormGroup: UntypedFormGroup; uploadImageFormGroup: UntypedFormGroup;
@ -59,7 +61,7 @@ export class UploadImageDialogComponent extends
private imageService: ImageService, private imageService: ImageService,
@Inject(MAT_DIALOG_DATA) public data: UploadImageDialogData, @Inject(MAT_DIALOG_DATA) public data: UploadImageDialogData,
@SkipSelf() private errorStateMatcher: ErrorStateMatcher, @SkipSelf() private errorStateMatcher: ErrorStateMatcher,
public dialogRef: MatDialogRef<UploadImageDialogComponent, ImageResourceInfo>, public dialogRef: MatDialogRef<UploadImageDialogComponent, ImageResource>,
public fb: UntypedFormBuilder) { public fb: UntypedFormBuilder) {
super(store, router, dialogRef); super(store, router, dialogRef);
} }
@ -98,18 +100,20 @@ export class UploadImageDialogComponent extends
const file: File = this.uploadImageFormGroup.get('file').value; const file: File = this.uploadImageFormGroup.get('file').value;
if (this.uploadImage) { if (this.uploadImage) {
const title: string = this.uploadImageFormGroup.get('title').value; const title: string = this.uploadImageFormGroup.get('title').value;
this.imageService.uploadImage(file, title).subscribe( forkJoin([
(res) => { this.imageService.uploadImage(file, title),
this.dialogRef.close(res); blobToBase64(file)
} ]).subscribe(([imageInfo, base64]) => {
); this.dialogRef.close(Object.assign(imageInfo, {base64}));
});
} else { } else {
const image = this.data.image; const image = this.data.image;
this.imageService.updateImage(imageResourceType(image), image.resourceKey, file).subscribe( forkJoin([
(res) => { this.imageService.updateImage(imageResourceType(image), image.resourceKey, file),
this.dialogRef.close(res); blobToBase64(file)
} ]).subscribe(([imageInfo, base64]) => {
); this.dialogRef.close(Object.assign(imageInfo, {base64}));
});
} }
} }
} }

2
ui-ngx/src/app/shared/models/alarm.models.ts

@ -335,7 +335,7 @@ export class AlarmQueryV2 {
let query = this.affectedEntityId ? `/${this.affectedEntityId.entityType}/${this.affectedEntityId.id}` : ''; let query = this.affectedEntityId ? `/${this.affectedEntityId.entityType}/${this.affectedEntityId.id}` : '';
query += this.pageLink.toQuery(); query += this.pageLink.toQuery();
if (this.typeList && this.typeList.length) { if (this.typeList && this.typeList.length) {
query += `&typeList=${this.typeList.join(',')}`; query += `&typeList=${this.typeList.map(type => encodeURIComponent(type)).join(',')}`;
} }
if (this.statusList && this.statusList.length) { if (this.statusList && this.statusList.length) {
query += `&statusList=${this.statusList.join(',')}`; query += `&statusList=${this.statusList.join(',')}`;

12
ui-ngx/src/app/shared/models/constants.ts

@ -159,12 +159,12 @@ export const HelpLinks = {
otaUpdates: helpBaseUrl + '/docs/user-guide/ota-updates', otaUpdates: helpBaseUrl + '/docs/user-guide/ota-updates',
widgetTypes: helpBaseUrl + '/docs/user-guide/ui/widget-library/#widget-types', widgetTypes: helpBaseUrl + '/docs/user-guide/ui/widget-library/#widget-types',
widgetsBundles: helpBaseUrl + '/docs/user-guide/ui/widget-library/#widgets-library-bundles', widgetsBundles: helpBaseUrl + '/docs/user-guide/ui/widget-library/#widgets-library-bundles',
widgetsConfig: helpBaseUrl + '/docs/user-guide/widgets', widgetsConfig: helpBaseUrl + '/docs/user-guide/ui/widget-library',
widgetsConfigTimeseries: helpBaseUrl + '/docs/user-guide/widgets', widgetsConfigTimeseries: helpBaseUrl + '/docs/user-guide/ui/widget-library#time-series',
widgetsConfigLatest: helpBaseUrl + '/docs/user-guide/widgets', widgetsConfigLatest: helpBaseUrl + '/docs/user-guide/ui/widget-library#latest-values',
widgetsConfigRpc: helpBaseUrl + '/docs/user-guide/widgets', widgetsConfigRpc: helpBaseUrl + '/docs/user-guide/ui/widget-library#control-widget',
widgetsConfigAlarm: helpBaseUrl + '/docs/user-guide/widgets', widgetsConfigAlarm: helpBaseUrl + '/docs/user-guide/ui/widget-library#alarm-widget',
widgetsConfigStatic: helpBaseUrl + '/docs/user-guide/widgets', widgetsConfigStatic: helpBaseUrl + '/docs/user-guide/ui/widget-library#static',
ruleNodePushToCloud: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/action-nodes/#push-to-cloud', ruleNodePushToCloud: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/action-nodes/#push-to-cloud',
ruleNodePushToEdge: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/action-nodes/#push-to-edge', ruleNodePushToEdge: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/action-nodes/#push-to-edge',
queue: helpBaseUrl + '/docs/user-guide/queue', queue: helpBaseUrl + '/docs/user-guide/queue',

4
ui-ngx/src/app/shared/models/resource.models.ts

@ -86,6 +86,10 @@ export interface ImageResourceInfo extends TbResourceInfo<ImageDescriptor> {
publicLink?: string; publicLink?: string;
} }
export interface ImageResource extends ImageResourceInfo {
base64?: string;
}
export interface ImageExportData { export interface ImageExportData {
mediaType: string; mediaType: string;
fileName: string; fileName: string;

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

@ -1674,8 +1674,8 @@
"alarm-rule-mobile-dashboard-hint": "Used by mobile application as an alarm details dashboard", "alarm-rule-mobile-dashboard-hint": "Used by mobile application as an alarm details dashboard",
"alarm-rule-no-mobile-dashboard": "No dashboard selected", "alarm-rule-no-mobile-dashboard": "No dashboard selected",
"propagate-alarm": "Propagate alarm to related entities", "propagate-alarm": "Propagate alarm to related entities",
"alarm-rule-relation-types-list": "Relation types to propagate", "alarm-rule-relation-types-list": "Relation types",
"alarm-rule-relation-types-list-hint": "If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.", "alarm-rule-relation-types-list-hint": "Defines relation types to filter the related entities. If not set, the alarm will be propagated to all related entities.",
"propagate-alarm-to-owner": "Propagate alarm to entity owner (Customer or Tenant)", "propagate-alarm-to-owner": "Propagate alarm to entity owner (Customer or Tenant)",
"propagate-alarm-to-tenant": "Propagate alarm to Tenant", "propagate-alarm-to-tenant": "Propagate alarm to Tenant",
"alarm-rule-condition": "Alarm rule condition", "alarm-rule-condition": "Alarm rule condition",
@ -5333,7 +5333,8 @@
"date": "Date", "date": "Date",
"active-bars-color": "Active signal bars color", "active-bars-color": "Active signal bars color",
"inactive-bars-color": "Inactive signal bars color", "inactive-bars-color": "Inactive signal bars color",
"signal-strength-card-style": "Signal strength card style" "signal-strength-card-style": "Signal strength card style",
"no-signal-rssi-value": "\"No signal\" rssi value"
}, },
"chart": { "chart": {
"common-settings": "Common settings", "common-settings": "Common settings",

4
ui-ngx/src/assets/locale/locale.constant-ko_KR.json

@ -1007,8 +1007,8 @@
"condition-duration-time-unit-required": "Time unit is required.", "condition-duration-time-unit-required": "Time unit is required.",
"advanced-settings": "Advanced settings", "advanced-settings": "Advanced settings",
"propagate-alarm": "Propagate alarm", "propagate-alarm": "Propagate alarm",
"alarm-rule-relation-types-list": "Relation types to propagate", "alarm-rule-relation-types-list": "Relation types",
"alarm-rule-relation-types-list-hint": "If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.", "alarm-rule-relation-types-list-hint": "Defines relation types to filter the related entities. If not set, the alarm will be propagated to all related entities.",
"alarm-rule-condition": "Alarm rule condition", "alarm-rule-condition": "Alarm rule condition",
"enter-alarm-rule-condition-prompt": "Please add alarm rule condition", "enter-alarm-rule-condition-prompt": "Please add alarm rule condition",
"edit-alarm-rule-condition": "Edit alarm rule condition", "edit-alarm-rule-condition": "Edit alarm rule condition",

4
ui-ngx/src/assets/locale/locale.constant-sl_SI.json

@ -1007,8 +1007,8 @@
"condition-duration-time-unit-required": "Time unit is required.", "condition-duration-time-unit-required": "Time unit is required.",
"advanced-settings": "Advanced settings", "advanced-settings": "Advanced settings",
"propagate-alarm": "Propagate alarm", "propagate-alarm": "Propagate alarm",
"alarm-rule-relation-types-list": "Relation types to propagate", "alarm-rule-relation-types-list": "Relation types",
"alarm-rule-relation-types-list-hint": "If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.", "alarm-rule-relation-types-list-hint": "Defines relation types to filter the related entities. If not set, the alarm will be propagated to all related entities.",
"alarm-rule-condition": "Alarm rule condition", "alarm-rule-condition": "Alarm rule condition",
"enter-alarm-rule-condition-prompt": "Please add alarm rule condition", "enter-alarm-rule-condition-prompt": "Please add alarm rule condition",
"edit-alarm-rule-condition": "Edit alarm rule condition", "edit-alarm-rule-condition": "Edit alarm rule condition",

Loading…
Cancel
Save