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",
"hasBasicMode": true,
"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,
"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 org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.ProcessFailureStrategy;
import org.thingsboard.server.actors.TbActor;
import org.thingsboard.server.actors.TbActorCtx;
import org.thingsboard.server.actors.TbActorException;
@ -88,7 +89,7 @@ public class AppActor extends ContextAwareActor {
case APP_INIT_MSG:
break;
case PARTITION_CHANGE_MSG:
ctx.broadcastToChildren(msg);
ctx.broadcastToChildren(msg, true);
break;
case COMPONENT_LIFE_CYCLE_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 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() {
ruleChainsInitialized = true;
log.debug("[{}] Initializing rule chains", tenantId);
for (RuleChain ruleChain : new PageDataIterable<>(link -> ruleChainService.findTenantRuleChainsByType(tenantId, RuleChainType.CORE, link), ContextAwareActor.ENTITY_PACK_LIMIT)) {
RuleChainId ruleChainId = 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);
log.debug("[{}|{}] Rule Chain actor created.", ruleChainId.getEntityType(), ruleChainId.getId());
}
ruleChainsInitialized = true;
}
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)) {
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);
@Override
public ProcessFailureStrategy onProcessFailure(Throwable t) {
log.debug("[{}] Processing failure: ", getActorRef().getActorId(), t);
public ProcessFailureStrategy onProcessFailure(TbActorMsg msg, Throwable t) {
log.debug("[{}] Processing failure for msg {}", getActorRef().getActorId(), msg, 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
protected void onTbApplicationEvent(PartitionChangeEvent event) {
log.info("Received partition change event.");
this.appActor.tellWithHighPriority(new PartitionChangeMsg(event.getServiceType()));
appActor.tellWithHighPriority(new PartitionChangeMsg(event.getServiceType()));
}
@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 org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.ProcessFailureStrategy;
import org.thingsboard.server.actors.TbActor;
import org.thingsboard.server.actors.TbActorCtx;
import org.thingsboard.server.actors.TbActorException;
@ -183,7 +184,7 @@ public class TenantActor extends RuleChainManagerActor {
return;
}
TbMsg tbMsg = msg.getMsg();
if (getApiUsageState().isReExecEnabled() && ruleChainsInitialized) {
if (getApiUsageState().isReExecEnabled()) {
if (tbMsg.getRuleChainId() == null) {
if (getRootChainActor() != null) {
getRootChainActor().tell(msg);
@ -207,7 +208,7 @@ public class TenantActor extends RuleChainManagerActor {
}
private void onRuleChainMsg(RuleChainAwareMsg msg) {
if (getApiUsageState().isReExecEnabled() && ruleChainsInitialized) {
if (getApiUsageState().isReExecEnabled()) {
getOrCreateActor(msg.getRuleChainId()).tell(msg);
}
}
@ -319,6 +320,12 @@ public class TenantActor extends RuleChainManagerActor {
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 {
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) {
List<ListenableFuture<Void>> result = new ArrayList<>();
try {
if (uplinkMsg.getEntityDataCount() > 0) {
for (EntityDataProto entityData : uplinkMsg.getEntityDataList()) {
result.addAll(ctx.getTelemetryProcessor().processTelemetryMsg(edge.getTenantId(), entityData));
}
}
if (uplinkMsg.getDeviceProfileUpdateMsgCount() > 0) {
for (DeviceProfileUpdateMsg deviceProfileUpdateMsg : uplinkMsg.getDeviceProfileUpdateMsgList()) {
result.add(((DeviceProfileProcessor) ctx.getDeviceProfileEdgeProcessorFactory().getProcessorByEdgeVersion(this.edgeVersion))
@ -731,6 +726,17 @@ public final class EdgeGrpcSession implements Closeable {
.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) {
for (AlarmUpdateMsg alarmUpdateMsg : uplinkMsg.getAlarmUpdateMsgList()) {
result.add(((AlarmProcessor) ctx.getAlarmEdgeProcessorFactory().getProcessorByEdgeVersion(this.edgeVersion))
@ -743,12 +749,6 @@ public final class EdgeGrpcSession implements Closeable {
.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) {
for (RelationUpdateMsg relationUpdateMsg : uplinkMsg.getRelationUpdateMsgList()) {
result.add(((RelationProcessor) ctx.getRelationEdgeProcessorFactory().getProcessorByEdgeVersion(this.edgeVersion))
@ -763,7 +763,8 @@ public final class EdgeGrpcSession implements Closeable {
}
if (uplinkMsg.getResourceUpdateMsgCount() > 0) {
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) {

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.setDefault(assetProfileById.isDefault());
}
assetProfile.setDefault(false);
String assetProfileName = assetProfile.getName();
AssetProfile assetProfileByName = assetProfileService.findAssetProfileByName(tenantId, assetProfileName);
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.TenantId;
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.DeviceUpdateMsg;
import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
@ -63,13 +62,6 @@ public abstract class BaseDeviceProcessor extends BaseEdgeProcessor {
device.setId(deviceId);
}
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);
} catch (Exception 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());
try {
DeviceCredentials deviceCredentialsByDeviceId = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, device.getId());
if (deviceCredentialsByDeviceId == null) {
deviceCredentialsByDeviceId = new DeviceCredentials();
deviceCredentialsByDeviceId.setDeviceId(device.getId());
}
deviceCredentialsByDeviceId.setCredentialsType(deviceCredentials.getCredentialsType());
deviceCredentialsByDeviceId.setCredentialsId(deviceCredentials.getCredentialsId());
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 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
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
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);
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);
}
}
parentChildMap.values().forEach(parentChildren -> parentChildren.remove(actorId));
TbActorMailbox mailbox = actors.remove(actorId);
if (mailbox != 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);
}
default ProcessFailureStrategy onProcessFailure(Throwable t) {
default ProcessFailureStrategy onProcessFailure(TbActorMsg msg, Throwable t) {
if (t instanceof Error) {
return ProcessFailureStrategy.stop();
} 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, boolean highPriority);
void broadcastToChildrenByType(TbActorMsg msg, EntityType entityType);
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());
} catch (Throwable t) {
log.debug("[{}] Failed to process message: {}", selfId, msg, t);
ProcessFailureStrategy strategy = actor.onProcessFailure(t);
ProcessFailureStrategy strategy = actor.onProcessFailure(msg, t);
if (strategy.isStop()) {
system.stop(selfId);
}
@ -190,7 +190,12 @@ public final class TbActorMailbox implements TbActorCtx {
@Override
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

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, boolean highPriority);
void broadcastToChildren(TbActorId parent, Predicate<TbActorId> childFilter, TbActorMsg msg);
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.temporal.ChronoUnit;
import java.time.temporal.TemporalAccessor;
import java.util.Arrays;
import java.util.Locale;
import java.util.function.BiFunction;
@ -73,21 +72,37 @@ public class TbDate implements Serializable, Cloneable {
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);
}
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);
}
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);
}
public TbDate(int year, int month, int date, int hrs, int min, int second, int secondMilli, String... tz) {
ZoneId zoneId = tz.length > 0 ? ZoneId.of(Arrays.stream(tz).findFirst().get()) : ZoneId.systemDefault();
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) {
this(year, month, date, hrs, min, second, milliSecond, null);
}
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() {

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);
}
@Transactional
@Override
public Device saveDevice(Device device, boolean 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;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import org.hibernate.exception.ConstraintViolationException;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@ -57,7 +56,6 @@ import org.thingsboard.server.dao.service.validator.DeviceCredentialsDataValidat
import org.thingsboard.server.dao.tenant.TenantProfileService;
import java.nio.ByteBuffer;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -160,24 +158,6 @@ public class DeviceServiceTest extends AbstractServiceTest {
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
public void testShouldRollbackValidatedDeviceIfDeviceCredentialsValidationFailed() {
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);
}
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(

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

@ -16,7 +16,9 @@
export * from './admin.service';
export * from './alarm.service';
export * from './alarm-comment.service';
export * from './asset.service';
export * from './asset-profile.service';
export * from './attribute.service';
export * from './audit-log.service';
export * from './component-descriptor.service';
@ -24,12 +26,14 @@ export * from './customer.service';
export * from './dashboard.service';
export * from './device.service';
export * from './device-profile.service';
export * from './entities-version-control.service';
export * from './entity.service';
export * from './edge.service';
export * from './entity-relation.service';
export * from './entity-view.service';
export * from './event.service';
export * from './http-utils';
export * from './image.service';
export * from './notification.service';
export * from './oauth2.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 { fromEvent, Subscription } from 'rxjs';
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');
@ -127,7 +128,8 @@ export class AlarmFilterConfigComponent implements OnInit, OnDestroy, ControlVal
private translate: TranslateService,
private overlay: Overlay,
private nativeElement: ElementRef,
private viewContainerRef: ViewContainerRef) {
private viewContainerRef: ViewContainerRef,
private utils: UtilsService) {
}
ngOnInit(): void {
@ -298,7 +300,7 @@ export class AlarmFilterConfigComponent implements OnInit, OnDestroy, ControlVal
this.translate.instant(alarmSeverityTranslations.get(s))).join(', '));
}
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) {
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-chip-row>
<input matInput type="text" placeholder="{{'device-profile.alarm-rule-relation-types-list' | translate}}"
style="max-width: 200px;"
[matChipInputFor]="relationTypesChipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
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 {
this.ctx.$scope.signalStrengthWidget = this;
this.settings = {...signalStrengthDefaultSettings, ...this.ctx.settings};
this.layout = this.settings.layout;
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);
@ -404,5 +403,4 @@ export class SignalStrengthWidgetComponent implements OnInit, OnDestroy, AfterVi
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;
dateColor: string;
activeBarsColor: ColorSettings;
noSignalRssiValue: number;
inactiveBarsColor: string;
showTooltip: boolean;
showTooltipValue: boolean;
@ -91,6 +92,7 @@ export const signalStrengthDefaultSettings: SignalStrengthWidgetSettings = {
],
colorFunction: defaultColorFunction
},
noSignalRssiValue: -100,
inactiveBarsColor: 'rgba(224, 224, 224, 1)',
showTooltip: 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 : ''">
<div class="tb-form-row">
<div class="fixed-title-width">{{ 'widget-action.target-dashboard-state' | translate }}
{{widgetActionFormGroup.get('type').value === widgetActionType.openDashboardState ? '*' : ''}}</div>
<div class="fixed-title-width">
<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"
[class.tb-suffix-absolute]="!actionTypeFormGroup.get('targetDashboardStateId').value">
<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 { WidgetActionCallbacks } from '@home/components/widget/action/manage-widget-actions.component.models';
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 { DashboardService } from '@core/http/dashboard.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, [])
);
if (type === WidgetActionType.openDashboard) {
const targetDashboardId = action ? action.targetDashboardId : null;
this.actionTypeFormGroup.addControl(
'openNewBrowserTab',
this.fb.control(action ? action.openNewBrowserTab : false, [])
);
this.actionTypeFormGroup.addControl(
'targetDashboardId',
this.fb.control(action ? action.targetDashboardId : null,
[Validators.required])
this.fb.control(targetDashboardId, [Validators.required])
);
this.setupSelectedDashboardStateIds();
this.setupSelectedDashboardStateIds(targetDashboardId);
} else {
if (type === WidgetActionType.openDashboardState) {
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.actionTypeFormGroup.get('targetDashboardId').valueChanges.pipe(
startWith(targetDashboardId),
tap((dashboardId) => {
if (!dashboardId) {
this.actionTypeFormGroup.get('targetDashboardStateId')
@ -400,7 +401,12 @@ export class WidgetActionComponent implements ControlValueAccessor, OnInit, Vali
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-title" translate>widgets.background.background-settings</div>
<div class="tb-form-panel tb-background-form-panel">
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<div class="tb-form-panel-title" translate>widgets.background.background</div>
<tb-toggle-select formControlName="type" fxFlex selectMediaBreakpoint="xs">
<tb-toggle-option *ngFor="let type of backgroundTypes"
[value]="type">
{{ backgroundTypeTranslationsMap.get(type) | translate }}
</tb-toggle-option>
</tb-toggle-select>
</div>
<tb-gallery-image-input [fxShow]="backgroundSettingsFormGroup.get('type').value === backgroundType.image" formControlName="imageUrl"></tb-gallery-image-input>
<div [fxShow]="backgroundSettingsFormGroup.get('type').value === backgroundType.color" class="tb-form-row space-between tb-background-color-field">
<div translate>widgets.color.color</div>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>
</div>
</div>
<div class="tb-form-panel" formGroupName="overlay">
<div class="tb-form-panel-title" translate>widgets.background.overlay</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 class="tb-background-settings-panel-content">
<div class="tb-form-panel tb-background-form-panel">
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<div class="tb-form-panel-title" translate>widgets.background.background</div>
<tb-toggle-select formControlName="type" fxFlex selectMediaBreakpoint="xs">
<tb-toggle-option *ngFor="let type of backgroundTypes"
[value]="type">
{{ backgroundTypeTranslationsMap.get(type) | translate }}
</tb-toggle-option>
</tb-toggle-select>
</div>
<tb-gallery-image-input [fxShow]="backgroundSettingsFormGroup.get('type').value === backgroundType.image"
formControlName="imageUrl"></tb-gallery-image-input>
<div [fxShow]="backgroundSettingsFormGroup.get('type').value === backgroundType.color"
class="tb-form-row space-between tb-background-color-field">
<div translate>widgets.color.color</div>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>
</div>
</div>
</div>
<div class="tb-background-settings-preview">
<div class="tb-background-settings-preview-title" translate>
widgets.background.preview
<div class="tb-form-panel" formGroupName="overlay">
<div class="tb-form-panel-title" translate>widgets.background.overlay</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 class="tb-background-settings-preview-box mat-elevation-z4" [style]="backgroundStyle$ | async">
<div class="tb-background-settings-preview-overlay" [style]="overlayStyle">
<div class="tb-background-settings-preview">
<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>

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

@ -67,6 +67,14 @@
left: 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 {
height: 40px;
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
};
const backgroundSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, BackgroundSettingsPanelComponent, 'left', true, null,
this.viewContainerRef, BackgroundSettingsPanelComponent, ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], true, null,
ctx,
{},
{}, {}, 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>
</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 class="tb-form-panel tb-slide-toggle">
<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, []],
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 { MillisecondsToTimeStringPipe } from '@shared/pipe/milliseconds-to-time-string.pipe';
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>>(
[
['broadcastService', BroadcastService],
['deviceService', DeviceService],
['alarmService', AlarmService],
['alarmCommentService', AlarmCommentService],
['assetService', AssetService],
['entityViewService', EntityViewService],
['customerService', CustomerService],
@ -66,6 +73,7 @@ export const ServicesMap = new Map<string, Type<any>>(
['translate', TranslateService],
['http', HttpClient],
['router', Router],
['imageService', ImageService],
['importExport', ImportExportService],
['deviceProfileService', DeviceProfileService],
['assetProfileService', AssetProfileService],
@ -74,7 +82,11 @@ export const ServicesMap = new Map<string, Type<any>>(
['resourceService', ResourceService],
['twoFactorAuthenticationService', TwoFactorAuthenticationService],
['telemetryWsService', TelemetryWebsocketService],
['tenantService', TenantService],
['tenantProfileService', TenantProfileService],
['userSettingsService', UserSettingsService],
['uiSettingsService', UiSettingsService],
['usageInfoService', UsageInfoService],
['notificationService', NotificationService]
]
);

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

@ -70,6 +70,8 @@
formControlName="targets"
subType="{{ ruleNotificationForm.get('triggerType').value }}"
entityType="{{ entityType.NOTIFICATION_TARGET }}"
labelText="{{ 'notification.recipients' | translate }}"
requiredText="{{ 'notification.recipients-required' | translate }}"
placeholderText="{{ 'notification.recipient' | translate }}">
<button #createTargetButton
mat-button color="primary" matSuffix
@ -453,6 +455,7 @@
<fieldset class="fields-group tb-margin-before-field">
<legend translate>notification.filter</legend>
<tb-entity-list labelText="{{'edge.edge-instances' | translate}}"
hint="{{ 'notification.edge-list-rule-hint' | translate }}"
[entityType]="entityType.EDGE"
formControlName="edges">
</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>
</ng-template>
<mat-step [stepControl]="notificationRequestForm">
<ng-template matStepLabel>{{ 'notification.compose' | translate }}</ng-template>
<ng-template matStepLabel>{{ 'notification.setup' | translate }}</ng-template>
<form [formGroup]="notificationRequestForm">
<div fxLayoutAlign="center">
<tb-toggle-select formControlName="useTemplate" appearance="fill">
@ -140,20 +140,24 @@
<mat-step *ngIf="!notificationRequestForm.get('useTemplate').value"
[stepControl]="notificationTemplateConfigurationForm" #composeStep=matStep>
<ng-template matStepLabel>{{ 'notification.compose' | translate }}</ng-template>
<form [formGroup]="notificationTemplateConfigurationForm">
<tb-template-configuration
[notificationType]="templateNotificationForm.get('notificationType').value"
[predefinedDeliveryMethodsTemplate]="deliveryConfiguration"
[interacted]="composeStep.interacted"
formControlName="deliveryMethodsTemplates">
</tb-template-configuration>
</form>
<ng-template matStepContent>
<form [formGroup]="notificationTemplateConfigurationForm">
<tb-template-configuration
notificationType="{{ notificationType.GENERAL }}"
[predefinedDeliveryMethodsTemplate]="deliveryConfiguration"
[interacted]="composeStep.interacted"
formControlName="deliveryMethodsTemplates">
</tb-template-configuration>
</form>
</ng-template>
</mat-step>
<mat-step>
<ng-template matStepLabel>{{ 'notification.review' | translate }}</ng-template>
<mat-progress-spinner color="warn" mode="indeterminate"
strokeWidth="5" *ngIf="(isLoading$ | async) && !preview">
</mat-progress-spinner>
<div class="tb-loader" *ngIf="(isLoading$ | async) && !preview">
<mat-progress-spinner color="warn" mode="indeterminate"
strokeWidth="5">
</mat-progress-spinner>
</div>
<div *ngIf="preview" class="tb-form-panel no-padding no-border">
<section class="preview-group tb-form-panel stroked no-gap">
<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;
}
.tb-loader {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 400px;
max-height: 100%;
}
.preview-group {
&.notification {
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.patchValue(this.data.request, {emitEvent: false});
this.notificationRequestForm.get('template.name').setValue(guid());
this.notificationRequestForm.get('template.notificationType').setValue(NotificationType.GENERAL);
let useTemplate = true;
if (isDefinedAndNotNull(this.data.request.template)) {
useTemplate = false;
@ -155,6 +156,7 @@ export class SentNotificationDialogComponent extends
}, {emitEvent: false});
}
this.notificationRequestForm.get('useTemplate').setValue(useTemplate, {onlySelf : true});
this.deliveryConfiguration = this.templateNotificationForm.get('configuration.deliveryMethodsTemplates').value;
}
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 {
position: sticky;
top: 0;
z-index: 1;
z-index: 5;
padding-bottom: 12px;
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 [stepControl]="notificationTemplateConfigurationForm" #composeStep=matStep>
<ng-template matStepLabel>{{ 'notification.compose' | translate }}</ng-template>
<form [formGroup]="notificationTemplateConfigurationForm">
<tb-template-configuration
[notificationType]="templateNotificationForm.get('notificationType').value"
[predefinedDeliveryMethodsTemplate]="deliveryConfiguration"
[interacted]="composeStep.interacted"
formControlName="deliveryMethodsTemplates">
</tb-template-configuration>
</form>
<ng-template matStepContent>
<form [formGroup]="notificationTemplateConfigurationForm">
<tb-template-configuration
[notificationType]="templateNotificationForm.get('notificationType').value"
[predefinedDeliveryMethodsTemplate]="deliveryConfiguration"
[interacted]="composeStep.interacted"
formControlName="deliveryMethodsTemplates">
</tb-template-configuration>
</form>
</ng-template>
</mat-step>
</mat-horizontal-stepper>
</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({
deliveryMethodsTemplates: this.templateNotification.configuration.deliveryMethodsTemplates
}, {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;"
#entitySubtypeInput
(focusin)="onFocus()"
(blur)="addOnBlur($event)"
formControlName="entitySubtype"
matAutocompleteOrigin
matChipInputAddOnBlur
#origin="matAutocompleteOrigin"
[matAutocompleteConnectedTo]="origin"
[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('');
}
}
addOnBlur(event: FocusEvent) {
if (!event.relatedTarget) {
return;
}
const value = this.entitySubtypeInput.nativeElement.value;
this.chipAdd({value} as MatChipInputEvent);
}
remove(entitySubtype: string) {
const index = this.entitySubtypeList.indexOf(entitySubtype);
if (index >= 0) {

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

@ -14,8 +14,7 @@
/// limitations under the License.
///
import { Component, Inject, OnInit, SkipSelf } from '@angular/core';
import { ErrorStateMatcher } from '@angular/material/core';
import { Component, Inject, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog';
import { Store } from '@ngrx/store';
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 { Router } from '@angular/router';
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 {
UploadImageDialogComponent,
UploadImageDialogData
@ -143,7 +142,7 @@ export class ImageDialogComponent extends
$event.stopPropagation();
}
this.dialog.open<UploadImageDialogComponent, UploadImageDialogData,
ImageResourceInfo>(UploadImageDialogComponent, {
ImageResource>(UploadImageDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
@ -153,9 +152,15 @@ export class ImageDialogComponent extends
if (result) {
this.imageChanged = true;
this.image = result;
this.imagePreviewData = {
url: this.image.public ? `${this.image.publicLink}?ts=${new Date().getTime()}` : this.image.link
};
let url;
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 { Router } from '@angular/router';
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 { forkJoin } from 'rxjs';
import { blobToBase64 } from '@core/utils';
export interface UploadImageDialogData {
image?: ImageResourceInfo;
@ -44,7 +46,7 @@ export interface UploadImageDialogData {
styleUrls: []
})
export class UploadImageDialogComponent extends
DialogComponent<UploadImageDialogComponent, ImageResourceInfo> implements OnInit, ErrorStateMatcher {
DialogComponent<UploadImageDialogComponent, ImageResource> implements OnInit, ErrorStateMatcher {
uploadImageFormGroup: UntypedFormGroup;
@ -59,7 +61,7 @@ export class UploadImageDialogComponent extends
private imageService: ImageService,
@Inject(MAT_DIALOG_DATA) public data: UploadImageDialogData,
@SkipSelf() private errorStateMatcher: ErrorStateMatcher,
public dialogRef: MatDialogRef<UploadImageDialogComponent, ImageResourceInfo>,
public dialogRef: MatDialogRef<UploadImageDialogComponent, ImageResource>,
public fb: UntypedFormBuilder) {
super(store, router, dialogRef);
}
@ -98,18 +100,20 @@ export class UploadImageDialogComponent extends
const file: File = this.uploadImageFormGroup.get('file').value;
if (this.uploadImage) {
const title: string = this.uploadImageFormGroup.get('title').value;
this.imageService.uploadImage(file, title).subscribe(
(res) => {
this.dialogRef.close(res);
}
);
forkJoin([
this.imageService.uploadImage(file, title),
blobToBase64(file)
]).subscribe(([imageInfo, base64]) => {
this.dialogRef.close(Object.assign(imageInfo, {base64}));
});
} else {
const image = this.data.image;
this.imageService.updateImage(imageResourceType(image), image.resourceKey, file).subscribe(
(res) => {
this.dialogRef.close(res);
}
);
forkJoin([
this.imageService.updateImage(imageResourceType(image), image.resourceKey, file),
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}` : '';
query += this.pageLink.toQuery();
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) {
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',
widgetTypes: helpBaseUrl + '/docs/user-guide/ui/widget-library/#widget-types',
widgetsBundles: helpBaseUrl + '/docs/user-guide/ui/widget-library/#widgets-library-bundles',
widgetsConfig: helpBaseUrl + '/docs/user-guide/widgets',
widgetsConfigTimeseries: helpBaseUrl + '/docs/user-guide/widgets',
widgetsConfigLatest: helpBaseUrl + '/docs/user-guide/widgets',
widgetsConfigRpc: helpBaseUrl + '/docs/user-guide/widgets',
widgetsConfigAlarm: helpBaseUrl + '/docs/user-guide/widgets',
widgetsConfigStatic: helpBaseUrl + '/docs/user-guide/widgets',
widgetsConfig: helpBaseUrl + '/docs/user-guide/ui/widget-library',
widgetsConfigTimeseries: helpBaseUrl + '/docs/user-guide/ui/widget-library#time-series',
widgetsConfigLatest: helpBaseUrl + '/docs/user-guide/ui/widget-library#latest-values',
widgetsConfigRpc: helpBaseUrl + '/docs/user-guide/ui/widget-library#control-widget',
widgetsConfigAlarm: helpBaseUrl + '/docs/user-guide/ui/widget-library#alarm-widget',
widgetsConfigStatic: helpBaseUrl + '/docs/user-guide/ui/widget-library#static',
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',
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;
}
export interface ImageResource extends ImageResourceInfo {
base64?: string;
}
export interface ImageExportData {
mediaType: 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-no-mobile-dashboard": "No dashboard selected",
"propagate-alarm": "Propagate alarm to related entities",
"alarm-rule-relation-types-list": "Relation types to propagate",
"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": "Relation types",
"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-tenant": "Propagate alarm to Tenant",
"alarm-rule-condition": "Alarm rule condition",
@ -5333,7 +5333,8 @@
"date": "Date",
"active-bars-color": "Active 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": {
"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.",
"advanced-settings": "Advanced settings",
"propagate-alarm": "Propagate alarm",
"alarm-rule-relation-types-list": "Relation types to propagate",
"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": "Relation types",
"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",
"enter-alarm-rule-condition-prompt": "Please add 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.",
"advanced-settings": "Advanced settings",
"propagate-alarm": "Propagate alarm",
"alarm-rule-relation-types-list": "Relation types to propagate",
"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": "Relation types",
"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",
"enter-alarm-rule-condition-prompt": "Please add alarm rule condition",
"edit-alarm-rule-condition": "Edit alarm rule condition",

Loading…
Cancel
Save