Browse Source

Merge remote-tracking branch 'upstream/develop/3.4' into feature/edge-converters-integration

pull/6600/head
Volodymyr Babak 4 years ago
parent
commit
ad47ba3feb
  1. 4
      application/src/main/data/json/system/widget_bundles/charts.json
  2. 41
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  3. 38
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java
  4. 1
      application/src/main/resources/thingsboard.yml
  5. 8
      application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java
  6. 2
      application/src/test/java/org/thingsboard/server/service/cluster/routing/HashPartitionServiceTest.java
  7. 4
      common/coap-server/src/main/java/org/thingsboard/server/coapserver/CoapServerContext.java
  8. 2
      common/coap-server/src/main/java/org/thingsboard/server/coapserver/CoapServerService.java
  9. 5
      common/coap-server/src/main/java/org/thingsboard/server/coapserver/DefaultCoapServerService.java
  10. 12
      common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java
  11. 4
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/TransportPayloadTypeConfiguration.java
  12. 2
      common/message/src/test/java/org/thingsboard/server/common/msg/tools/RateLimitsTest.java
  13. 39
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java
  14. 20
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java
  15. 11
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java
  16. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java
  17. 1
      transport/coap/src/main/resources/tb-coap-transport.yml
  18. 2
      ui-ngx/src/app/core/services/menu.service.ts
  19. 2
      ui-ngx/src/app/modules/home/components/profile/tenant-profile-data.component.ts
  20. 2
      ui-ngx/src/app/modules/home/components/queue/queue-form.component.html
  21. 4
      ui-ngx/src/app/modules/home/components/queue/queue-form.component.scss
  22. 2
      ui-ngx/src/app/modules/home/components/queue/queue-form.component.ts
  23. 2
      ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts
  24. 52
      ui-ngx/src/app/modules/home/components/widget/lib/canvas-digital-gauge.ts
  25. 3
      ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator-panel.component.scss
  26. 6
      ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.ts
  27. 33
      ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts
  28. 1
      ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts
  29. 14
      ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts
  30. 2
      ui-ngx/src/app/modules/home/models/services.map.ts
  31. 2
      ui-ngx/src/app/modules/home/models/widget-component.models.ts
  32. 11
      ui-ngx/src/app/modules/home/pages/admin/queue/queues-table-config.resolver.ts
  33. 4
      ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts
  34. 7
      ui-ngx/src/app/shared/components/directives/tb-json-to-string.directive.ts
  35. 4
      ui-ngx/src/app/shared/components/json-object-edit.component.ts
  36. 19
      ui-ngx/src/app/shared/models/ace/service-completion.models.ts
  37. 2
      ui-ngx/src/app/shared/models/constants.ts
  38. 2
      ui-ngx/src/app/shared/models/queue.models.ts
  39. 2094
      ui-ngx/src/assets/locale/locale.constant-es_ES.json
  40. 18
      ui-ngx/src/assets/locale/locale.constant-it_IT.json
  41. 197
      ui-ngx/src/assets/locale/locale.constant-zh_CN.json

4
application/src/main/data/json/system/widget_bundles/charts.json

File diff suppressed because one or more lines are too long

41
application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java

@ -571,6 +571,22 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.3.4", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn);
log.info("Loading queues...");
try {
if (!CollectionUtils.isEmpty(queueConfig.getQueues())) {
queueConfig.getQueues().forEach(queueSettings -> {
Queue queue = queueConfigToQueue(queueSettings);
Queue existing = queueService.findQueueByTenantIdAndName(queue.getTenantId(), queue.getName());
if (existing == null) {
queueService.saveQueue(queue);
}
});
} else {
systemDataLoaderService.createQueues();
}
} catch (Exception e) {
}
log.info("Updating device profiles...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.3.4", "schema_update_device_profile.sql");
loadSql(schemaUpdateFile, conn);
@ -628,4 +644,29 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
return isOldSchema;
}
private Queue queueConfigToQueue(TbRuleEngineQueueConfiguration queueSettings) {
Queue queue = new Queue();
queue.setTenantId(TenantId.SYS_TENANT_ID);
queue.setName(queueSettings.getName());
queue.setTopic(queueSettings.getTopic());
queue.setPollInterval(queueSettings.getPollInterval());
queue.setPartitions(queueSettings.getPartitions());
queue.setPackProcessingTimeout(queueSettings.getPackProcessingTimeout());
SubmitStrategy submitStrategy = new SubmitStrategy();
submitStrategy.setBatchSize(queueSettings.getSubmitStrategy().getBatchSize());
submitStrategy.setType(SubmitStrategyType.valueOf(queueSettings.getSubmitStrategy().getType()));
queue.setSubmitStrategy(submitStrategy);
ProcessingStrategy processingStrategy = new ProcessingStrategy();
processingStrategy.setType(ProcessingStrategyType.valueOf(queueSettings.getProcessingStrategy().getType()));
processingStrategy.setRetries(queueSettings.getProcessingStrategy().getRetries());
processingStrategy.setFailurePercentage(queueSettings.getProcessingStrategy().getFailurePercentage());
processingStrategy.setPauseBetweenRetries(queueSettings.getProcessingStrategy().getPauseBetweenRetries());
processingStrategy.setMaxPauseBetweenRetries(queueSettings.getProcessingStrategy().getMaxPauseBetweenRetries());
queue.setProcessingStrategy(processingStrategy);
queue.setConsumerPerPartition(queueSettings.isConsumerPerPartition());
return queue;
}
}

38
application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java

@ -162,21 +162,6 @@ public class DefaultDataUpdateService implements DataUpdateService {
break;
case "3.3.4":
log.info("Updating data from version 3.3.4 to 3.4.0 ...");
log.info("Loading queues...");
try {
if (!CollectionUtils.isEmpty(queueConfig.getQueues())) {
queueConfig.getQueues().forEach(queueSettings -> {
Queue queue = queueConfigToQueue(queueSettings);
Queue existing = queueService.findQueueByTenantIdAndName(queue.getTenantId(), queue.getName());
if (existing == null) {
queueService.saveQueue(queue);
}
});
} else {
systemDataLoaderService.createQueues();
}
} catch (Exception e) {
}
tenantsProfileQueueConfigurationUpdater.updateEntities(null);
checkPointRuleNodesUpdater.updateEntities(null);
break;
@ -649,29 +634,6 @@ public class DefaultDataUpdateService implements DataUpdateService {
return mainQueueConfiguration;
}
private Queue queueConfigToQueue(TbRuleEngineQueueConfiguration queueSettings) {
Queue queue = new Queue();
queue.setTenantId(TenantId.SYS_TENANT_ID);
queue.setName(queueSettings.getName());
queue.setTopic(queueSettings.getTopic());
queue.setPollInterval(queueSettings.getPollInterval());
queue.setPartitions(queueSettings.getPartitions());
queue.setPackProcessingTimeout(queueSettings.getPackProcessingTimeout());
SubmitStrategy submitStrategy = new SubmitStrategy();
submitStrategy.setBatchSize(queueSettings.getSubmitStrategy().getBatchSize());
submitStrategy.setType(SubmitStrategyType.valueOf(queueSettings.getSubmitStrategy().getType()));
queue.setSubmitStrategy(submitStrategy);
ProcessingStrategy processingStrategy = new ProcessingStrategy();
processingStrategy.setType(ProcessingStrategyType.valueOf(queueSettings.getProcessingStrategy().getType()));
processingStrategy.setRetries(queueSettings.getProcessingStrategy().getRetries());
processingStrategy.setFailurePercentage(queueSettings.getProcessingStrategy().getFailurePercentage());
processingStrategy.setPauseBetweenRetries(queueSettings.getProcessingStrategy().getPauseBetweenRetries());
processingStrategy.setMaxPauseBetweenRetries(queueSettings.getProcessingStrategy().getMaxPauseBetweenRetries());
queue.setProcessingStrategy(processingStrategy);
queue.setConsumerPerPartition(queueSettings.isConsumerPerPartition());
return queue;
}
private final PaginatedUpdater<String, RuleNode> checkPointRuleNodesUpdater =
new PaginatedUpdater<>() {

1
application/src/main/resources/thingsboard.yml

@ -706,6 +706,7 @@ transport:
bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}"
bind_port: "${COAP_BIND_PORT:5683}"
timeout: "${COAP_TIMEOUT:10000}"
piggyback_timeout: "${COAP_PIGGYBACK_TIMEOUT:500}"
psm_activity_timer: "${COAP_PSM_ACTIVITY_TIMER:10000}"
paging_transmission_window: "${COAP_PAGING_TRANSMISSION_WINDOW:10000}"
dtls:

8
application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java

@ -58,6 +58,8 @@ import org.thingsboard.server.common.data.device.profile.AlarmRule;
import org.thingsboard.server.common.data.device.profile.AllowCreateNewDevicesDeviceProfileProvisionConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.JsonTransportPayloadConfiguration;
import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
@ -199,7 +201,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
private void installation() throws Exception {
edge = doPost("/api/edge", constructEdge("Test Edge", "test"), Edge.class);
DeviceProfile deviceProfile = this.createDeviceProfile(CUSTOM_DEVICE_PROFILE_NAME);
MqttDeviceProfileTransportConfiguration transportConfiguration = new MqttDeviceProfileTransportConfiguration();
transportConfiguration.setTransportPayloadTypeConfiguration(new JsonTransportPayloadConfiguration());
DeviceProfile deviceProfile = this.createDeviceProfile(CUSTOM_DEVICE_PROFILE_NAME, transportConfiguration);
extendDeviceProfileData(deviceProfile);
doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);

2
application/src/test/java/org/thingsboard/server/service/cluster/routing/HashPartitionServiceTest.java

@ -85,7 +85,9 @@ public class HashPartitionServiceTest {
.addAllServiceTypes(Collections.singletonList(ServiceType.TB_CORE.name()))
.build());
}
clusterRoutingService.init();
clusterRoutingService.partitionsInit();
clusterRoutingService.recalculatePartitions(currentServer, otherServers);
}

4
common/coap-server/src/main/java/org/thingsboard/server/coapserver/CoapServerContext.java

@ -38,6 +38,10 @@ public class CoapServerContext {
@Value("${transport.coap.timeout}")
private Long timeout;
@Getter
@Value("${transport.coap.piggyback_timeout}")
private Long piggybackTimeout;
@Getter
@Value("${transport.coap.psm_activity_timer:10000}")
private long psmActivityTimer;

2
common/coap-server/src/main/java/org/thingsboard/server/coapserver/CoapServerService.java

@ -29,4 +29,6 @@ public interface CoapServerService {
long getTimeout();
long getPiggybackTimeout();
}

5
common/coap-server/src/main/java/org/thingsboard/server/coapserver/DefaultCoapServerService.java

@ -88,6 +88,11 @@ public class DefaultCoapServerService implements CoapServerService {
return coapServerContext.getTimeout();
}
@Override
public long getPiggybackTimeout() {
return coapServerContext.getPiggybackTimeout();
}
private CoapServer createCoapServer() throws UnknownHostException {
Configuration networkConfig = new Configuration();
networkConfig.set(CoapConfig.BLOCKWISE_STRICT_BLOCK2_OPTION, true);

12
common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java

@ -16,6 +16,7 @@
package org.thingsboard.server.common.data;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ -78,6 +79,8 @@ public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements H
"If present, the specified queue will be used to store all unprocessed messages related to device, including telemetry, attribute updates, etc. " +
"Otherwise, the 'Main' queue will be used to store those messages.")
private QueueId defaultQueueId;
private String defaultQueueName;
@Valid
private transient DeviceProfileData profileData;
@JsonIgnore
@ -168,4 +171,13 @@ public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements H
}
}
@JsonIgnore
public String getDefaultQueueName() {
return defaultQueueName;
}
@JsonProperty
public void setDefaultQueueName(String defaultQueueName) {
this.defaultQueueName = defaultQueueName;
}
}

4
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/TransportPayloadTypeConfiguration.java

@ -21,6 +21,8 @@ import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.thingsboard.server.common.data.TransportPayloadType;
import java.io.Serializable;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
@ -29,7 +31,7 @@ import org.thingsboard.server.common.data.TransportPayloadType;
@JsonSubTypes({
@JsonSubTypes.Type(value = JsonTransportPayloadConfiguration.class, name = "JSON"),
@JsonSubTypes.Type(value = ProtoTransportPayloadConfiguration.class, name = "PROTOBUF")})
public interface TransportPayloadTypeConfiguration {
public interface TransportPayloadTypeConfiguration extends Serializable {
@JsonIgnore
TransportPayloadType getTransportPayloadType();

2
common/message/src/test/java/org/thingsboard/server/common/msg/tools/RateLimitsTest.java

@ -67,7 +67,7 @@ public class RateLimitsTest {
assertThat(rateLimits.tryConsume()).as("new token is available").isFalse();
int expectedRefillTime = period * 1000;
int gap = 300;
int gap = 500;
await("tokens refill for rate limit " + rateLimitConfig)
.atLeast(expectedRefillTime - gap, TimeUnit.MILLISECONDS)

39
common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java

@ -90,20 +90,37 @@ public class HashPartitionService implements PartitionService {
@PostConstruct
public void init() {
this.hashFunction = forName(hashFunctionName);
QueueKey coreKey = new QueueKey(ServiceType.TB_CORE);
partitionSizesMap.put(coreKey, corePartitions);
partitionTopicsMap.put(coreKey, coreTopic);
if (!isTransport(serviceInfoProvider.getServiceType())) {
doInitRuleEnginePartitions();
}
}
@AfterStartUp(order = AfterStartUp.QUEUE_INFO_INITIALIZATION)
public void partitionsInit() {
QueueKey coreKey = new QueueKey(ServiceType.TB_CORE);
partitionSizesMap.put(coreKey, corePartitions);
partitionTopicsMap.put(coreKey, coreTopic);
if (isTransport(serviceInfoProvider.getServiceType())) {
doInitRuleEnginePartitions();
}
}
List<QueueRoutingInfo> queueRoutingInfoList;
private void doInitRuleEnginePartitions() {
List<QueueRoutingInfo> queueRoutingInfoList = getQueueRoutingInfos();
queueRoutingInfoList.forEach(queue -> {
QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, queue);
partitionTopicsMap.put(queueKey, queue.getQueueTopic());
partitionSizesMap.put(queueKey, queue.getPartitions());
queuesById.put(queue.getQueueId(), queue);
});
}
private List<QueueRoutingInfo> getQueueRoutingInfos() {
List<QueueRoutingInfo> queueRoutingInfoList;
String serviceType = serviceInfoProvider.getServiceType();
if ("tb-transport".equals(serviceType)) {
if (isTransport(serviceType)) {
//If transport started earlier than tb-core
int getQueuesRetries = 10;
while (true) {
@ -128,13 +145,11 @@ public class HashPartitionService implements PartitionService {
} else {
queueRoutingInfoList = queueRoutingInfoService.getAllQueuesRoutingInfo();
}
return queueRoutingInfoList;
}
queueRoutingInfoList.forEach(queue -> {
QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, queue);
partitionTopicsMap.put(queueKey, queue.getQueueTopic());
partitionSizesMap.put(queueKey, queue.getPartitions());
queuesById.put(queue.getQueueId(), queue);
});
private boolean isTransport(String serviceType) {
return "tb-transport".equals(serviceType);
}
@Override

20
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java

@ -69,6 +69,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
private final ConcurrentMap<InetSocketAddress, TbCoapDtlsSessionInfo> dtlsSessionsMap;
private final long timeout;
private final long piggybackTimeout;
private final CoapClientContext clients;
public CoapTransportResource(CoapTransportContext ctx, CoapServerService coapServerService, String name) {
@ -77,6 +78,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
this.addObserver(new CoapResourceObserver());
this.dtlsSessionsMap = coapServerService.getDtlsSessionsMap();
this.timeout = coapServerService.getTimeout();
this.piggybackTimeout = coapServerService.getPiggybackTimeout();
this.clients = ctx.getClientContext();
long sessionReportTimeout = ctx.getSessionReportTimeout();
ctx.getScheduler().scheduleAtFixedRate(clients::reportActivity, new Random().nextInt((int) sessionReportTimeout), sessionReportTimeout, TimeUnit.MILLISECONDS);
@ -169,7 +171,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
}
private void processProvision(CoapExchange exchange) {
exchange.accept();
deferAccept(exchange);
try {
UUID sessionId = UUID.randomUUID();
log.trace("[{}] Processing provision publish msg [{}]!", sessionId, exchange.advanced().getRequest());
@ -195,7 +197,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
private void processRequest(CoapExchange exchange, SessionMsgType type) {
log.trace("Processing {}", exchange.advanced().getRequest());
exchange.accept();
deferAccept(exchange);
Exchange advanced = exchange.advanced();
Request request = advanced.getRequest();
@ -346,6 +348,20 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
new CoapNoOpCallback(exchange));
}
/**
* Send an empty ACK if we are unable to send the full response within the timeout.
* If the full response is transmitted before the timeout this will not do anything.
* If this is triggered the full response will be sent in a separate CON/NON message.
* Essentially this allows the use of piggybacked responses.
*/
private void deferAccept(CoapExchange exchange) {
if (piggybackTimeout > 0) {
transportContext.getScheduler().schedule(exchange::accept, piggybackTimeout, TimeUnit.MILLISECONDS);
} else {
exchange.accept();
}
}
private UUID toSessionId(TransportProtos.SessionInfoProto sessionInfoProto) {
return new UUID(sessionInfoProto.getSessionIdMSB(), sessionInfoProto.getSessionIdLSB());
}

11
dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java

@ -37,8 +37,10 @@ import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.dao.entity.AbstractCachedEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
@ -71,6 +73,9 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService<Device
@Autowired
private DataValidator<DeviceProfile> deviceProfileValidator;
@Autowired
private QueueService queueService;
private final Lock findOrCreateLock = new ReentrantLock();
@TransactionalEventListener(classes = DeviceProfileEvictEvent.class)
@ -119,6 +124,12 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService<Device
DeviceProfile oldDeviceProfile = deviceProfileValidator.validate(deviceProfile, DeviceProfile::getTenantId);
DeviceProfile savedDeviceProfile;
try {
if (deviceProfile.getDefaultQueueId() == null && StringUtils.isNotEmpty(deviceProfile.getDefaultQueueName())) {
Queue existing = queueService.findQueueByTenantIdAndName(deviceProfile.getTenantId(), deviceProfile.getDefaultQueueName());
if (existing != null) {
deviceProfile.setDefaultQueueId(existing.getId());
}
}
savedDeviceProfile = deviceProfileDao.saveAndFlush(deviceProfile.getTenantId(), deviceProfile);
publishEvictEvent(new DeviceProfileEvictEvent(savedDeviceProfile.getTenantId(), savedDeviceProfile.getName(),
oldDeviceProfile != null ? oldDeviceProfile.getName() : null, savedDeviceProfile.getId(), savedDeviceProfile.isDefault()));

4
dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java

@ -369,7 +369,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
try {
return jdbcTemplate.queryForObject(countQuery, ctx, Long.class);
} finally {
queryLog.logQuery(ctx, ctx.getQuery(), System.currentTimeMillis() - startTs);
queryLog.logQuery(ctx, countQuery, System.currentTimeMillis() - startTs);
}
});
}
@ -481,7 +481,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
try {
rows = jdbcTemplate.queryForList(dataQuery, ctx);
} finally {
queryLog.logQuery(ctx, countQuery, System.currentTimeMillis() - startTs);
queryLog.logQuery(ctx, dataQuery, System.currentTimeMillis() - startTs);
}
return EntityDataAdapter.createEntityData(pageLink, selectionMapping, rows, totalElements);
});

1
transport/coap/src/main/resources/tb-coap-transport.yml

@ -89,6 +89,7 @@ transport:
bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}"
bind_port: "${COAP_BIND_PORT:5683}"
timeout: "${COAP_TIMEOUT:10000}"
piggyback_timeout: "${COAP_PIGGYBACK_TIMEOUT:500}"
psm_activity_timer: "${COAP_PSM_ACTIVITY_TIMER:10000}"
paging_transmission_window: "${COAP_PAGING_TRANSMISSION_WINDOW:10000}"
dtls:

2
ui-ngx/src/app/core/services/menu.service.ts

@ -108,7 +108,7 @@ export class MenuService {
name: 'admin.system-settings',
type: 'toggle',
path: '/settings',
height: '280px',
height: '320px',
icon: 'settings',
pages: [
{

2
ui-ngx/src/app/modules/home/components/profile/tenant-profile-data.component.ts

@ -95,7 +95,7 @@ export class TenantProfileDataComponent implements ControlValueAccessor, OnInit,
if (this.tenantProfileDataFormGroup.valid) {
tenantProfileData = this.tenantProfileDataFormGroup.getRawValue();
}
this.propagateChange(tenantProfileData.configuration);
this.propagateChange(tenantProfileData?.configuration);
}
}

2
ui-ngx/src/app/modules/home/components/queue/queue-form.component.html

@ -16,7 +16,7 @@
-->
<form [formGroup]="queueFormGroup" fxLayout="column" fxLayoutGap="0.5em">
<form [formGroup]="queueFormGroup" fxLayout="column">
<mat-form-field class="mat-block">
<mat-label translate>admin.queue-name</mat-label>
<input matInput formControlName="name" required>

4
ui-ngx/src/app/modules/home/components/queue/queue-form.component.scss

@ -17,6 +17,10 @@
.queue-strategy {
padding-bottom: 16px;
.mat-expansion-panel-header {
height: 50px;
}
.mat-expansion-panel-body {
padding-bottom: 0 !important;
}

2
ui-ngx/src/app/modules/home/components/queue/queue-form.component.ts

@ -158,6 +158,8 @@ export class QueueFormComponent implements ControlValueAccessor, OnInit, OnDestr
this.modelValue = value;
if (isDefinedAndNotNull(this.modelValue)) {
this.queueFormGroup.patchValue(this.modelValue, {emitEvent: false});
this.queueFormGroup.get('additionalInfo').get('description')
.patchValue(this.modelValue.additionalInfo?.description, {emitEvent: false});
this.submitStrategyTypeChanged();
}
if (!this.disabled && !this.queueFormGroup.valid) {

2
ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts

@ -38,6 +38,7 @@ import { EntityRelationService } from '@core/http/entity-relation.service';
import { EntityService } from '@core/http/entity.service';
import { DialogService } from '@core/services/dialog.service';
import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service';
import { ResourceService } from '@core/http/resource.service';
import { DatePipe } from '@angular/common';
import { TranslateService } from '@ngx-translate/core';
import { DomSanitizer } from '@angular/platform-browser';
@ -76,6 +77,7 @@ export class DynamicWidgetComponent extends PageComponent implements IDynamicWid
this.ctx.entityService = $injector.get(EntityService);
this.ctx.dialogs = $injector.get(DialogService);
this.ctx.customDialog = $injector.get(CustomDialogService);
this.ctx.resourceService = $injector.get(ResourceService);
this.ctx.date = $injector.get(DatePipe);
this.ctx.translate = $injector.get(TranslateService);
this.ctx.http = $injector.get(HttpClient);

52
ui-ngx/src/app/modules/home/components/widget/lib/canvas-digital-gauge.ts

@ -47,7 +47,6 @@ export interface CanvasDigitalGaugeOptions extends GenericOptions {
gaugeColor?: string;
levelColors?: levelColors;
symbol?: string;
label?: string;
hideValue?: boolean;
hideMinMax?: boolean;
fontTitle?: string;
@ -86,6 +85,9 @@ export interface CanvasDigitalGaugeOptions extends GenericOptions {
colorTicks?: string;
tickWidth?: number;
labelTimestamp?: string
unitTitle?: string;
showUnitTitle?: boolean;
showTimestamp?: boolean;
}
@ -100,7 +102,6 @@ const defaultDigitalGaugeOptions: CanvasDigitalGaugeOptions = { ...GenericOption
levelColors: ['blue'],
symbol: '',
label: '',
hideValue: false,
hideMinMax: false,
@ -145,6 +146,7 @@ interface DigitalGaugeCanvasRenderingContext2D extends CanvasRenderingContext2D
}
interface BarDimensions {
timeseriesLabelY?: number;
baseX: number;
baseY: number;
width: number;
@ -362,7 +364,7 @@ export class CanvasDigitalGauge extends BaseGauge {
const options = this.options as CanvasDigitalGaugeOptions;
const elementClone = canvas.elementClone as HTMLCanvasElementClone;
if (!elementClone.initialized) {
const context = canvas.contextClone;
const context: DigitalGaugeCanvasRenderingContext2D = canvas.contextClone;
// clear the cache
context.clearRect(x, y, w, h);
@ -378,8 +380,8 @@ export class CanvasDigitalGauge extends BaseGauge {
drawDigitalTitle(context, options);
if (!options.showTimestamp) {
drawDigitalLabel(context, options);
if (options.showUnitTitle) {
drawDigitalLabel(context, options, options.unitTitle, 'labelY');
}
drawDigitalMinMax(context, options);
@ -406,7 +408,7 @@ export class CanvasDigitalGauge extends BaseGauge {
drawDigitalValue(context, options, this.elementValueClone.renderedValue);
if (options.showTimestamp) {
drawDigitalLabel(context, options);
drawDigitalLabel(context, options, options.labelTimestamp, 'timeseriesLabelY');
this.elementValueClone.renderedTimestamp = this.timestamp;
}
@ -564,11 +566,12 @@ function barDimensions(context: DigitalGaugeCanvasRenderingContext2D,
if (options.gaugeType === 'donut') {
bd.fontValueBaseline = 'middle';
if (options.label && options.label.length > 0) {
if (options.showUnitTitle || options.showTimestamp) {
const valueHeight = determineFontHeight(options, 'Value', bd.fontSizeFactor).height;
const labelHeight = determineFontHeight(options, 'Label', bd.fontSizeFactor).height;
const total = valueHeight + labelHeight;
bd.labelY = bd.Cy + total / 2;
bd.timeseriesLabelY = determineTimeseriesLabelY(options, bd.labelY, bd.fontSizeFactor)
bd.valueY = bd.Cy - total / 2 + valueHeight / 2;
} else {
bd.valueY = bd.Cy;
@ -577,6 +580,7 @@ function barDimensions(context: DigitalGaugeCanvasRenderingContext2D,
bd.titleY = bd.Cy - bd.Ro - 12 * bd.fontSizeFactor;
bd.valueY = bd.Cy;
bd.labelY = bd.Cy + (8 + options.fontLabelSize) * bd.fontSizeFactor;
bd.timeseriesLabelY = determineTimeseriesLabelY(options, bd.labelY, bd.fontSizeFactor)
bd.minY = bd.maxY = bd.labelY;
if (options.roundedLineCap) {
bd.minY += bd.strokeWidth / 2;
@ -595,8 +599,9 @@ function barDimensions(context: DigitalGaugeCanvasRenderingContext2D,
bd.barTop = bd.valueY + 8 * bd.fontSizeFactor;
bd.barBottom = bd.barTop + bd.strokeWidth;
if (options.hideMinMax && options.label === '') {
if (options.hideMinMax && !options.showUnitTitle && !options.showTimestamp) {
bd.labelY = bd.barBottom;
bd.timeseriesLabelY = determineTimeseriesLabelY(options, bd.labelY, bd.fontSizeFactor)
bd.barLeft = bd.origBaseX + options.fontMinMaxSize / 3 * bd.fontSizeFactor;
bd.barRight = bd.origBaseX + w + /*bd.width*/ -options.fontMinMaxSize / 3 * bd.fontSizeFactor;
} else {
@ -609,6 +614,7 @@ function barDimensions(context: DigitalGaugeCanvasRenderingContext2D,
bd.barLeft = bd.minX;
bd.barRight = bd.maxX;
bd.labelY = bd.barBottom + (8 + options.fontLabelSize) * bd.fontSizeFactor;
bd.timeseriesLabelY = determineTimeseriesLabelY(options, bd.labelY, bd.fontSizeFactor)
bd.minY = bd.maxY = bd.labelY;
}
} else if (options.gaugeType === 'verticalBar') {
@ -618,14 +624,14 @@ function barDimensions(context: DigitalGaugeCanvasRenderingContext2D,
bd.valueY = bd.titleBottom + (options.hideValue ? 0 : options.fontValueSize * bd.fontSizeFactor);
bd.barTop = bd.valueY + 8 * bd.fontSizeFactor;
bd.labelY = bd.baseY + bd.height - 16;
if (options.label === '') {
bd.barBottom = bd.labelY;
bd.labelY = bd.baseY + bd.height;
bd.timeseriesLabelY = determineTimeseriesLabelY(options, bd.labelY, bd.fontSizeFactor)
if (options.showUnitTitle || options.showTimestamp) {
bd.barBottom = bd.labelY - options.fontLabelSize * bd.fontSizeFactor;
} else {
bd.barBottom = bd.labelY - (8 + options.fontLabelSize) * bd.fontSizeFactor;
bd.barBottom = bd.labelY
}
bd.minX = bd.maxX =
bd.baseX + bd.width / 2 + bd.strokeWidth / 2 + options.fontMinMaxSize / 3 * bd.fontSizeFactor;
bd.minX = bd.maxX = bd.baseX + bd.width / 2 + bd.strokeWidth / 2 + options.fontMinMaxSize / 3 * bd.fontSizeFactor;
bd.minY = bd.barBottom;
bd.maxY = bd.barTop;
bd.fontMinMaxBaseline = 'middle';
@ -657,6 +663,14 @@ function barDimensions(context: DigitalGaugeCanvasRenderingContext2D,
return bd;
}
function determineTimeseriesLabelY(options: CanvasDigitalGaugeOptions, labelY: number, fontSizeFactor: number){
if (options.showUnitTitle) {
return labelY + options.fontLabelSize * fontSizeFactor * 1.2;
} else {
return labelY;
}
}
function determineFontHeight(options: CanvasDigitalGaugeOptions, target: string, baseSize: number): FontHeightInfo {
const fontStyleStr = 'font-style:' + options['font' + target + 'Style'] + ';font-weight:' +
options['font' + target + 'Weight'] + ';font-size:' +
@ -751,22 +765,22 @@ function drawDigitalTitle(context: DigitalGaugeCanvasRenderingContext2D, options
context.restore();
}
function drawDigitalLabel(context: DigitalGaugeCanvasRenderingContext2D, options: CanvasDigitalGaugeOptions) {
if (!options.label || options.label === '') {
function drawDigitalLabel(context: DigitalGaugeCanvasRenderingContext2D, options: CanvasDigitalGaugeOptions, text: string, nameTextY: string) {
if (!text || text === '') {
return;
}
const {labelY, baseX, width, fontSizeFactor} =
const {baseX, width, fontSizeFactor} =
context.barDimensions;
const textX = Math.round(baseX + width / 2);
const textY = labelY;
const textY = context.barDimensions[nameTextY];
context.save();
context.textAlign = 'center';
context.font = Drawings.font(options, 'Label', fontSizeFactor);
context.lineWidth = 0;
drawText(context, options, 'Label', options.label, textX, textY);
drawText(context, options, 'Label', text, textX, textY);
context.restore();
}

3
ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator-panel.component.scss

@ -29,7 +29,6 @@
.mat-padding {
padding: 16px;
min-width: 560px;
}
}

6
ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.ts

@ -65,6 +65,7 @@ export class TbCanvasDigitalGauge {
(settings.unitTitle && settings.unitTitle.length > 0 ?
settings.unitTitle : dataKey.label) : '');
this.localSettings.showUnitTitle = settings.showUnitTitle === true;
this.localSettings.showTimestamp = settings.showTimestamp === true;
this.localSettings.timestampFormat = settings.timestampFormat && settings.timestampFormat.length ?
settings.timestampFormat : 'yyyy-MM-dd HH:mm:ss';
@ -181,7 +182,8 @@ export class TbCanvasDigitalGauge {
roundedLineCap: this.localSettings.roundedLineCap,
symbol: this.localSettings.units,
label: this.localSettings.unitTitle,
unitTitle: this.localSettings.unitTitle,
showUnitTitle: this.localSettings.showUnitTitle,
showTimestamp: this.localSettings.showTimestamp,
hideValue: this.localSettings.hideValue,
hideMinMax: this.localSettings.hideMinMax,
@ -400,7 +402,7 @@ export class TbCanvasDigitalGauge {
if (this.localSettings.showTimestamp) {
timestamp = tvPair[0];
const filter = this.ctx.$injector.get(DatePipe);
(this.gauge.options as CanvasDigitalGaugeOptions).label =
(this.gauge.options as CanvasDigitalGaugeOptions).labelTimestamp =
filter.transform(timestamp, this.localSettings.timestampFormat);
}
const value = tvPair[1];

33
ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts

@ -312,6 +312,9 @@ export class TbFlot {
if (this.settings.stroke) {
this.options.series.pie.stroke.color = this.settings.stroke.color || '#fff';
this.options.series.pie.stroke.width = this.settings.stroke.width || 0;
if (this.options.series.pie.stroke.width) {
this.scalingPieRadius();
}
}
if (this.options.series.pie.label.show) {
@ -690,22 +693,34 @@ export class TbFlot {
}
}
private scalingPieRadius() {
let scalingLine;
this.ctx.width > this.ctx.height ? scalingLine = this.ctx.height : scalingLine = this.ctx.width;
let changeRadius = this.options.series.pie.stroke.width / scalingLine;
this.options.series.pie.radius = changeRadius < 1 ? this.settings.radius - changeRadius : 0;
}
public resize() {
if (this.resizeTimeoutHandle) {
clearTimeout(this.resizeTimeoutHandle);
this.resizeTimeoutHandle = null;
}
if (this.plot && this.plotInited) {
const width = this.$element.width();
const height = this.$element.height();
if (width && height) {
this.plot.resize();
if (this.chartType !== 'pie') {
this.plot.setupGrid();
}
this.plot.draw();
if (this.chartType === 'pie' && this.settings.stroke?.width) {
this.scalingPieRadius();
this.redrawPlot();
} else {
this.resizeTimeoutHandle = setTimeout(this.resize.bind(this), 30);
const width = this.$element.width();
const height = this.$element.height();
if (width && height) {
this.plot.resize();
if (this.chartType !== 'pie') {
this.plot.setupGrid();
}
this.plot.draw();
} else {
this.resizeTimeoutHandle = setTimeout(this.resize.bind(this), 30);
}
}
}
}

1
ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts

@ -222,6 +222,7 @@ export class ImageMap extends LeafletMap {
maxZoom,
scrollWheelZoom: !this.options.disableScrollZooming,
center,
doubleClickZoom: !this.options.disableZoomControl,
zoomControl: !this.options.disableZoomControl,
zoom: 1,
crs: L.CRS.Simple,

14
ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts

@ -112,14 +112,12 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
item => this.clearIncorrectFirsLastDatapoint(item)).filter(arr => arr.length);
this.interpolatedTimeData.length = 0;
this.formattedInterpolatedTimeData.length = 0;
if (this.historicalData.length) {
const prevMinTime = this.minTime;
const prevMaxTime = this.maxTime;
this.calculateIntervals();
const currentTime = this.calculateCurrentTime(prevMinTime, prevMaxTime);
if (currentTime !== this.currentTime) {
this.timeUpdated(currentTime);
}
const prevMinTime = this.minTime;
const prevMaxTime = this.maxTime;
this.calculateIntervals();
const currentTime = this.calculateCurrentTime(prevMinTime, prevMaxTime);
if (currentTime !== this.currentTime) {
this.timeUpdated(currentTime);
}
this.mapWidget.map.map?.invalidateSize();
this.mapWidget.map.setLoading(false);

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

@ -36,6 +36,7 @@ import { BroadcastService } from '@core/services/broadcast.service';
import { ImportExportService } from '@home/components/import-export/import-export.service';
import { DeviceProfileService } from '@core/http/device-profile.service';
import { OtaPackageService } from '@core/http/ota-package.service';
import { ResourceService } from '@core/http/resource.service';
import { TwoFactorAuthenticationService } from '@core/http/two-factor-authentication.service';
export const ServicesMap = new Map<string, Type<any>>(
@ -61,6 +62,7 @@ export const ServicesMap = new Map<string, Type<any>>(
['importExport', ImportExportService],
['deviceProfileService', DeviceProfileService],
['otaPackageService', OtaPackageService],
['resourceService', ResourceService],
['twoFactorAuthenticationService', TwoFactorAuthenticationService]
]
);

2
ui-ngx/src/app/modules/home/models/widget-component.models.ts

@ -72,6 +72,7 @@ import { EntityRelationService } from '@core/http/entity-relation.service';
import { EntityService } from '@core/http/entity.service';
import { DialogService } from '@core/services/dialog.service';
import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service';
import { ResourceService } from '@core/http/resource.service';
import { DatePipe } from '@angular/common';
import { TranslateService } from '@ngx-translate/core';
import { PageLink } from '@shared/models/page/page-link';
@ -167,6 +168,7 @@ export class WidgetContext {
entityService: EntityService;
dialogs: DialogService;
customDialog: CustomDialogService;
resourceService: ResourceService;
date: DatePipe;
translate: TranslateService;
http: HttpClient;

11
ui-ngx/src/app/modules/home/pages/admin/queue/queues-table-config.resolver.ts

@ -17,7 +17,12 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, Router } from '@angular/router';
import { EntityTableColumn, EntityTableConfig } from '@home/models/entity/entities-table-config.models';
import { QueueInfo, ServiceType } from '@shared/models/queue.models';
import {
QueueInfo,
QueueProcessingStrategyTypesMap,
QueueSubmitStrategyTypesMap,
ServiceType
} from '@shared/models/queue.models';
import { select, Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { BroadcastService } from '@core/services/broadcast.service';
@ -83,14 +88,14 @@ export class QueuesTableConfigResolver implements Resolve<EntityTableConfig<Queu
new EntityTableColumn<QueueInfo>('partitions', 'admin.queue-partitions', '25%'),
new EntityTableColumn<QueueInfo>('submitStrategy', 'admin.queue-submit-strategy', '25%',
(entity: QueueInfo) => {
return entity.submitStrategy.type;
return this.translate.instant(QueueSubmitStrategyTypesMap.get(entity.submitStrategy.type).label);
},
() => ({}),
false
),
new EntityTableColumn<QueueInfo>('processingStrategy', 'admin.queue-processing-strategy', '25%',
(entity: QueueInfo) => {
return entity.processingStrategy.type;
return this.translate.instant(QueueProcessingStrategyTypesMap.get(entity.processingStrategy.type).label);
},
() => ({}),
false

4
ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts

@ -89,6 +89,10 @@ export class RuleChainPageComponent extends PageComponent
return this.isDirtyValue || this.isImport;
}
set isDirty(value: boolean) {
this.isDirtyValue = value;
}
@HostBinding('style.width') width = '100%';
@HostBinding('style.height') height = '100%';

7
ui-ngx/src/app/shared/components/directives/tb-json-to-string.directive.ts

@ -26,6 +26,7 @@ import {
Validator
} from '@angular/forms';
import { ErrorStateMatcher } from '@angular/material/core';
import { isObject } from "@core/utils";
@Directive({
selector: '[tb-json-to-string]',
@ -53,7 +54,11 @@ export class TbJsonToStringDirective implements ControlValueAccessor, Validator,
@HostListener('input', ['$event.target.value']) input(newValue: any): void {
try {
this.data = JSON.parse(newValue);
this.parseError = false;
if (isObject(this.data)) {
this.parseError = false;
} else {
this.parseError = true;
}
} catch (e) {
this.parseError = true;
}

4
ui-ngx/src/app/shared/components/json-object-edit.component.ts

@ -22,7 +22,7 @@ import { ActionNotificationHide, ActionNotificationShow } from '@core/notificati
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
import { guid, isDefinedAndNotNull, isLiteralObject, isUndefined } from '@core/utils';
import { guid, isDefinedAndNotNull, isObject, isUndefined } from '@core/utils';
import { ResizeObserver } from '@juggle/resize-observer';
import { getAce } from '@shared/models/ace/ace.models';
@ -259,7 +259,7 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va
if (this.contentValue && this.contentValue.length > 0) {
try {
data = JSON.parse(this.contentValue);
if (!isLiteralObject(data)) {
if (!isObject(data)) {
throw new TypeError(`Value is not a valid JSON`);
}
this.objectValid = true;

19
ui-ngx/src/app/shared/models/ace/service-completion.models.ts

@ -100,6 +100,8 @@ export const importEntitiesResultInfoHref = '<a href="https://github.com/thingsb
export const customDialogComponentHref = '<a href="https://github.com/thingsboard/thingsboard/blob/master/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog.component.ts#L48">CustomDialogComponent</a>';
export const resourceInfoHref = '<a https://github.com/thingsboard/thingsboard/blob/b033b51712244d08e0f5e0beb8be60c9f8fa4cd2/ui-ngx/src/app/shared/models/resource.models.ts#L51">Resource info</a>';
export const pageLinkArg: FunctionArg = {
name: 'pageLink',
type: '<a href="https://github.com/thingsboard/thingsboard/blob/13e6b10b7ab830e64d31b99614a9d95a1a25928a/ui-ngx/src/app/shared/models/page/page-link.ts#L68">PageLink</a>',
@ -1300,6 +1302,23 @@ export const serviceCompletions: TbEditorCompletions = {
},
}
},
resourceService: {
description: 'Resource Service API<br>' +
'See <a href="https://github.com/thingsboard/thingsboard/blob/b033b51712244d08e0f5e0beb8be60c9f8fa4cd2/ui-ngx/src/app/core/http/resource.service.ts#L29">ResourceService</a> for API reference.',
meta: 'service',
type: '<a href="https://github.com/thingsboard/thingsboard/blob/b033b51712244d08e0f5e0beb8be60c9f8fa4cd2/ui-ngx/src/app/core/http/resource.service.ts#L29">ResourceService</a>',
children: {
getResources: {
description: 'Find resources by search text',
meta: 'function',
args: [
pageLinkArg,
requestConfigArg
],
return: observablePageDataReturnType(resourceInfoHref)
},
}
},
dialogs: {
description: 'Dialogs Service API<br>' +
'See <a href="https://github.com/thingsboard/thingsboard/blob/13e6b10b7ab830e64d31b99614a9d95a1a25928a/ui-ngx/src/app/core/services/dialog.service.ts#L39">DialogService</a> for API reference.',

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

@ -202,7 +202,7 @@ export const valueTypesMap = new Map<ValueType, ValueTypeData>(
ValueType.JSON,
{
name: 'value.json',
icon: 'mdi:json'
icon: 'mdi:code-json'
}
]
]

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

@ -75,7 +75,7 @@ export const QueueProcessingStrategyTypesMap = new Map<QueueProcessingStrategyTy
[
[QueueProcessingStrategyTypes.RETRY_FAILED_AND_TIMED_OUT, {
label: 'queue.strategies.retry-failed-and-timeout-label',
hint: 'queue.strategies.retry-failed-and-timout-hint',
hint: 'queue.strategies.retry-failed-and-timeout-hint',
}],
[QueueProcessingStrategyTypes.SKIP_ALL_FAILURES, {
label: 'queue.strategies.skip-all-failures-label',

2094
ui-ngx/src/assets/locale/locale.constant-es_ES.json

File diff suppressed because it is too large

18
ui-ngx/src/assets/locale/locale.constant-it_IT.json

@ -123,10 +123,10 @@
"UNACK": "Non riconosciuto"
},
"display-status": {
"ACTIVE_UNACK": "Active Unacknowledged",
"ACTIVE_ACK": "Active Acknowledged",
"CLEARED_UNACK": "Cleared Unacknowledged",
"CLEARED_ACK": "Cleared Acknowledged"
"ACTIVE_UNACK": "Attivo Non riconosciuto",
"ACTIVE_ACK": "Attivo Riconosciuto",
"CLEARED_UNACK": "Cancellato Non riconosciuto",
"CLEARED_ACK": "Cancellato Riconosciuto"
},
"no-alarms-prompt": "Nessun allarme trovato",
"created-time": "Orario di creazione",
@ -1066,9 +1066,9 @@
"modbus-register-address": "Indirizzo registro",
"modbus-register-address-range": "Indirizzo registro deve essere compreso tra 0 e 65535.",
"modbus-register-bit-index": "Bit index",
"modbus-register-bit-index-range": "Bit index should be in a range from 0 to 15.",
"modbus-register-bit-index-range": "Bit index deve essere compreso tra 0 e 15.",
"modbus-register-count": "Register count",
"modbus-register-count-range": "Register count should be a positive value.",
"modbus-register-count-range": "Register count dovrebbe essereun valore positivo.",
"modbus-byte-order": "Byte order",
"sync": {
"status": "Stato",
@ -1307,13 +1307,13 @@
"type-enrichment": "Enrichment",
"type-enrichment-details": "Aggiungi informazioni addizionali nei metadati del messaggio",
"type-transformation": "Trasformazione",
"type-transformation-details": "Change Message payload and Metadata",
"type-transformation-details": "Modifica payload messaggio e metadati",
"type-action": "Azioni",
"type-action-details": "Perform special action",
"type-external": "External",
"type-external-details": "Interagisci con un sistema esterno",
"type-rule-chain": "Rule Chain",
"type-rule-chain-details": "Forwards incoming messages to specified Rule Chain",
"type-rule-chain-details": "Inoltra i messaggi in arrivo ad una specifica Rule Chain",
"type-input": "Input",
"type-input-details": "Logical input of Rule Chain, forwards incoming messages to next related Rule Node",
"type-unknown": "Sconosciuto",
@ -1502,7 +1502,7 @@
"widget-action": {
"header-button": "Widget header button",
"open-dashboard-state": "Navigate to new dashboard state",
"update-dashboard-state": "Update current dashboard state",
"update-dashboard-state": "Aggiorna lo stato della dashboard attuale",
"open-dashboard": "Navigate to other dashboard",
"custom": "Custom action",
"target-dashboard-state": "Target dashboard state",

197
ui-ngx/src/assets/locale/locale.constant-zh_CN.json

@ -312,6 +312,11 @@
"filter-type-device-type": "设备类型",
"filter-type-device-type-and-name-description": "类型为 '{{deviceType}}' 且以 '{{prefix}}' 开头的设备",
"filter-type-device-type-description": "类型为 '{{deviceType}}' 的设备",
"filter-type-edge-type": "边缘类型",
"filter-type-edge-type-description": "类型为 '{{edgeType}}' 的边缘",
"filter-type-edge-type-and-name-description": "类型为 '{{edgeType}}' 且以 '{{prefix}}' 开头的边缘",
"filter-type-edge-search-query": "边缘搜索查询",
"filter-type-edge-search-query-description": "类型为 {{edgeTypes}} 且具有 {{relationType}} 关联 {{direction}} {{rootEntity}} 的边缘",
"filter-type-entity-list": "实体列表",
"filter-type-entity-name": "实体名称",
"filter-type-entity-view-search-query": "实体视图搜索查询",
@ -409,6 +414,8 @@
"assets": "资产",
"assign-asset-to-customer": "将资产分配给客户",
"assign-asset-to-customer-text": "请选择要分配给客户的资产",
"assign-asset-to-edge-text": "请选择要分配给边缘的资产",
"assign-asset-to-edge-title": "将资产分配给边缘",
"assign-assets": "分配资产",
"assign-assets-text": "分配 { count, plural, 1 {# 个资产} other {# 个资产} } 给客户",
"assign-new-asset": "分配新资产",
@ -451,6 +458,9 @@
"type": "类型",
"type-required": "类型必填。",
"unassign-asset": "未分配资产",
"unassign-asset-from-edge": "取消分配边缘",
"unassign-asset-from-edge-text": "确认后,所有选定的资产将被分配,边缘无法访问。",
"unassign-asset-from-edge-title": "您确定要取消对'{{assetName}}'资产的分配吗?",
"unassign-asset-text": "确认后,资产将未分配,客户无法访问。",
"unassign-asset-title": "您确定要取消对'{{assetName}}'资产的分配吗?",
"unassign-assets": "取消分配资产",
@ -458,6 +468,9 @@
"unassign-assets-text": "确认后,所有选定的资产将被分配,客户无法访问。",
"unassign-assets-title": "您确定要取消分配 { count, plural, 1 {# 个资产} other {# 个资产} }吗?",
"unassign-from-customer": "取消分配客户",
"unassign-assets-from-edge": "取消分配边缘",
"unassign-assets-from-edge-text": "确认后,所有选定的资产将被分配,边缘无法访问。",
"unassign-assets-from-edge-title": "您确定要取消分配 { count, plural, 1 {1 资产} other {# 个资产} }吗?",
"view-assets": "查看资产"
},
"attribute": {
@ -512,6 +525,7 @@
"type-alarm-clear": "已清除",
"type-assigned-from-tenant": "从租户分配",
"type-assigned-to-customer": "分配给客户",
"type-assigned-to-edge": "分配给边缘",
"type-assigned-to-tenant": "分配给租户",
"type-attributes-deleted": "删除属性",
"type-attributes-read": "读取属性",
@ -532,6 +546,7 @@
"type-timeseries-deleted": "遥测数据已删除",
"type-timeseries-updated": "遥测数据已更新",
"type-unassigned-from-customer": "未分配给客户",
"type-unassigned-from-edge": "未分配给边缘",
"type-updated": "更新",
"user": "用户"
},
@ -603,6 +618,7 @@
"description": "说明",
"details": "详情",
"devices": "客户设备",
"edges": "客户边缘实例",
"entity-views": "客户实体视图",
"events": "事件",
"idCopiedMessage": "客户ID已复制到粘贴板",
@ -610,12 +626,15 @@
"manage-customer-assets": "管理客户资产",
"manage-customer-dashboards": "管理客户仪表板",
"manage-customer-devices": "管理客户设备",
"manage-customer-edges": "管理客户边缘",
"manage-customer-users": "管理客户用户",
"manage-dashboards": "管理仪表板",
"manage-devices": "管理设备",
"manage-edges": "管理边缘",
"manage-public-assets": "管理公共资产",
"manage-public-dashboards": "管理公共仪表板",
"manage-public-devices": "管理公共设备",
"manage-public-edges": "管理公共边缘",
"manage-users": "管理用户",
"management": "客户管理",
"no-customers-matching": "没有找到匹配 '{{entity}}' 的客户。",
@ -624,6 +643,7 @@
"public-dashboards": "公共仪表板",
"public-devices": "公共设备",
"public-entity-views": "公共实体视图",
"public-edges": "公共边缘",
"search": "查找客户",
"select-customer": "选择客户",
"select-default-customer": "选择默认的客户",
@ -639,6 +659,9 @@
"alias-resolution-error-title": "仪表板别名配置错误",
"assign-dashboard-to-customer": "将仪表板分配给客户",
"assign-dashboard-to-customer-text": "请选择要分配给客户的仪表板",
"assign-dashboard-to-edge": "将仪表板分配给边缘",
"assign-dashboard-to-edge-text": "请选择要分配给边缘的仪表板",
"assign-dashboard-to-edge-title": "将仪表板分配给边缘",
"assign-dashboards": "分配仪表板",
"assign-dashboards-text": "分配 { count, plural, 1 {# 个仪表板} other {# 个仪表板} } 给客户",
"assign-new-dashboard": "分配新的仪表板",
@ -773,6 +796,7 @@
"title-required": "标题必填。",
"toolbar-always-open": "工具栏常驻",
"unassign-dashboard": "取消分配仪表板",
"unassign-dashboard-from-edge-text": "确认后,所有选定的仪表板将被取消分配,边缘将无法访问。",
"unassign-dashboard-text": "确认后,面板将被取消分配,客户将无法访问。",
"unassign-dashboard-title": "您确定要取消分配仪表板 '{{dashboardTitle}}'吗?",
"unassign-dashboards": "取消分配仪表板",
@ -780,6 +804,8 @@
"unassign-dashboards-action-title": "取消分配此客户 { count, plural, 1 {# 个仪表板} other {# 个仪表板} }",
"unassign-dashboards-text": "确认后,所有选定的仪表板将被取消分配,客户将无法访问。",
"unassign-dashboards-title": "确定要取消分配仪表板 { count, plural, 1 {# 个仪表板} other {# 个仪表板} } 吗?",
"unassign-dashboards-from-edge-text": "确认后,所有选定的仪表板将被取消分配,边缘将无法访问。",
"unassign-dashboards-from-edge-title": "确定要取消分配仪表板 { count, plural, 1 {# 个仪表板} other {# 个仪表板} } 吗?",
"unassign-from-customer": "取消分配客户",
"unassign-from-customers": "客户未分配仪表板",
"unassign-from-customers-text": "请选择从仪表板中取消分配的客户",
@ -1021,6 +1047,8 @@
"any-device": "任意设备",
"assign-device-to-customer": "将设备分配给客户",
"assign-device-to-customer-text": "请选择要分配给客户的设备",
"assign-device-to-edge-text":"请选择要分配给边缘的设备",
"assign-device-to-edge-title": "将设备分配给边缘",
"assign-devices": "分配设备",
"assign-devices-text": "将 {count,plural,1 {# 个设备} other {# 个设备} }分配给客户",
"assign-new-device": "分配新设备",
@ -1106,8 +1134,13 @@
"unassign-device": "取消分配设备",
"unassign-device-text": "确认后,设备将被取消分配,客户将无法访问。",
"unassign-device-title": "您确定要取消分配设备 '{{deviceName}}'?",
"unassign-device-from-edge-text": "确认后,设备将被取消分配,边缘将无法访问。",
"unassign-device-from-edge-title": "您确定要取消分配设备 '{{deviceName}}'?",
"unassign-devices": "取消分配设备",
"unassign-devices-action-title": "取消分配此客户 {count,plural,1 {# 个设备} other {# 个设备} }",
"unassign-devices-from-edge": "取消分配边缘",
"unassign-devices-from-edge-text": "确认后,设备将被取消分配,边缘将无法访问。",
"unassign-devices-from-edge-title": "您确定要取消分配 { count, plural, 1 {1个 设备} other {# 个设备} } 吗?",
"unassign-devices-text": "确认后,所有选定的设备将被取消分配,并且客户将无法访问。",
"unassign-devices-title": "确定要取消分配 {count,plural,1 {# 个设备} other {# 个设备} } 吗?",
"unassign-from-customer": "取消分配客户",
@ -1133,6 +1166,133 @@
"column": "列",
"row": "排"
},
"edge": {
"add": "增加边缘",
"add-edge-text": "增加新的边缘",
"any-edge": "任何边缘",
"assets": "边缘资产",
"assign-edge-to-customer": "分配边缘给客户",
"assign-edge-to-customer-text": "请选择需要分配给边缘的客户",
"assign-new-edge": "分配新边缘",
"assign-to-customer": "分配给客户",
"assign-to-customer-text": "请选择需要分配给边缘的客户",
"assigned-to-customer": "分配给: {{customerTitle}}",
"assignedToCustomer": "分配给客户",
"copy-edge-key": "复制边缘键",
"copy-edge-secret": "复制边缘密钥",
"copy-id": "复制边缘编号",
"dashboard": "边缘仪表板",
"dashboards": "边缘仪表板",
"delete": "删除边缘",
"delete-edge-text": "当心, 确认后,边缘以及所有关联数据将不可恢复。",
"delete-edge-title": "您确定删除边缘 '{{edgeName}}'?",
"delete-edges-text": "当心, 确认后,选定的边缘以及所有关联数据将不可恢复。",
"delete-edges-title": "您确认删除边缘 { count, plural, 1 {1 个边缘} other {# 个边缘} }?",
"deployed": "已部署",
"description": "描述",
"details": "详情",
"devices": "边缘设备",
"downlinks": "下行",
"edge": "边缘",
"edge-assets": "边缘资产",
"edge-dashboards": "边缘仪表板",
"edge-details": "边缘详情",
"edge-devices": "边缘设备",
"edge-entity-views": "边缘实体视图",
"edge-file": "边缘文件",
"edge-instances": "边缘实例",
"edge-key": "边缘键",
"edge-key-copied-message": "边缘键已经被复制到剪切板",
"edge-public": "边缘公开",
"edge-required": "边缘必填。",
"edge-rulechains": "边缘规则链",
"edge-secret": "边缘密钥",
"edge-secret-copied-message": "边缘密钥已经被复制到剪切板",
"edge-type": "边缘类型",
"edge-type-list-empty": "没有选择边缘类型。",
"edge-type-required": "边缘类型必填。",
"edge-types": "边缘类型",
"enter-edge-type": "输入边缘类型",
"entity-id": "实体编号",
"entity-views": "边缘实体视图",
"event-action": "事件行动",
"events": "事件",
"id-copied-message": "边缘编号已经复制到剪切板",
"import": "导入边缘",
"label": "标签",
"label-max-length": "标签长度必须少于256个字符",
"load-entity-error": "加载数据失败,实体已经被删除。",
"make-private": "私有",
"make-private-edge-text": "确认后,边缘及其所有数据将被设为私有,不被其他人访问。",
"make-private-edge-title": "您确定要将边缘 '{{edgeName}}' 设为私有?",
"make-public": "公开",
"make-public-edge-text": "确认后,边缘及其所有数据将被设为公开并可被其他人访问。",
"make-public-edge-title": "您确定要将边缘 '{{edgeName}}' 设为公开吗?",
"management": "边缘管理",
"missing-related-rule-chains-text": "分配给边缘的规则链使用规则节点将消息转发给未分配给当前边缘的规则链。 <br><br> 缺少的规则链列表: <br> {{missingRuleChains}}",
"missing-related-rule-chains-title": "边缘缺少关联规则链",
"name": "名称",
"name-max-length": "名称长度必须少于256个字符",
"name-required": "名称必填。",
"name-starts-with": "边缘名称前缀",
"no-downlinks-prompt": "没有找到下行",
"no-edge-types-matching": "没有找到匹配的边缘类型 '{{entitySubtype}}'。",
"no-edges-matching": "没有找到匹配的边缘 '{{entity}}'。",
"no-edges-text": "没有找到边缘",
"pending": "待定",
"public": "公开",
"rulechain-templates": "规则链模版",
"rulechains": "规则链",
"search": "搜索边缘",
"select-edge-type": "选择边缘类型",
"selected-edges": "{ count, plural, 1 {1 个边缘} other {# 个边缘} } 被选中",
"sync": "同步边缘",
"sync-process-started-successfully": "同步处理开始成功!",
"type-max-length": "类型长度必须少于256个字符",
"unassign-edge-text": "确定后,边缘将被取消分配,并且客户将无法访问。",
"unassign-edge-title": "您确定取消分配边缘 '{{edgeName}}'?",
"unassign-edges-text": "确定后,所有选定的边缘将被取消分配,并且客户将无法访问。",
"unassign-edges-title": "您确定要取消分配 {count,plural,1 {1 个边缘} other {# 个边缘} } 吗?",
"unassign-from-customer": "取消分配客户",
"unassign-from-edge": "取消分配边缘",
"widget-datasource-error": "组件只支持边缘实体数据源"
},
"edge-event": {
"action-type-added": "增加",
"action-type-alarm-ack": "警告确认",
"action-type-alarm-clear": "警告清除",
"action-type-assigned-to-customer": "分配给客户",
"action-type-assigned-to-edge": "分配给边缘",
"action-type-attributes-deleted": "属性删除",
"action-type-attributes-updated": "属性更新",
"action-type-credentials-request": "认证请求",
"action-type-credentials-updated": "认证更新",
"action-type-deleted": "删除",
"action-type-entity-merge-request": "实体合并请求",
"action-type-post-attributes": "推送属性",
"action-type-relation-add-or-update": "关联增加或更新",
"action-type-relation-deleted": "关联删除",
"action-type-rpc-call": "RPC调用",
"action-type-timeseries-updated": "时序更新",
"action-type-unassigned-from-customer": "取消分配客户",
"action-type-unassigned-from-edge": "取消分配边缘",
"action-type-updated": "更新",
"type-admin-settings": "管理员设置",
"type-alarm": "告警",
"type-asset": "资产",
"type-customer": "客户",
"type-dashboard": "仪表板",
"type-device": "设备",
"type-device-profile": "设备概要",
"type-edge": "边缘",
"type-entity-view": "实体视图",
"type-relation": "关联",
"type-rule-chain": "规则链",
"type-rule-chain-metadata": "规则链元数据",
"type-user": "用户",
"type-widgets-bundle": "部件包",
"type-widgets-type": "部件类型"
},
"entity-field": {
"address": "地址",
"address2": "地址2",
@ -1160,6 +1320,9 @@
"any-entity-view": "任何实体视图",
"assign-entity-view-to-customer": "将实体视图分配给客户",
"assign-entity-view-to-customer-text": "请选择要分配给客户的实体视图",
"assign-entity-view-to-edge": "将实体视图分配给边缘",
"assign-entity-view-to-edge-text": "请选择要分配给边缘的实体视图",
"assign-entity-view-to-edge-title": "将实体视图分配给边缘",
"assign-entity-views": "分配实体视图",
"assign-entity-views-text": "分配 { count, plural, 1 {# 个实体视图} other {# 个实体视图} } 给客户",
"assign-new-entity-view": "分配新实体视图",
@ -1244,8 +1407,14 @@
"unassign-entity-view": "未分配实体视图",
"unassign-entity-view-text": "确认后,实体视图将未分配,客户无法访问。",
"unassign-entity-view-title": "您确定要取消对 '{{entityViewName}}'实体视图的分配吗?",
"unassign-entity-view-from-edge": "未分配实体视图",
"unassign-entity-view-from-edge-text": "确认后,实体视图将未分配,边缘无法访问。",
"unassign-entity-view-from-edge-title": "您确定要取消对 '{{entityViewName}}'实体视图的分配吗?",
"unassign-entity-views": "取消分配实体视图",
"unassign-entity-views-action-title": "从客户处取消分配{count,plural,1 {# 实体视图} other {# 实体视图} }",
"unassign-entity-views-from-edge-action-title": "从边缘处取消分配{count,plural,1 {# 实体视图} other {# 实体视图} }",
"unassign-entity-views-from-edge-text": "确认后,所有选定的实体视图将被分配,边缘无法访问。",
"unassign-entity-views-from-edge-title": "确定要取消分配 { count, plural, 1 {# 个实体视图} other {# 个实体视图} }吗?",
"unassign-entity-views-text": "确认后,所有选定的实体视图将被分配,客户无法访问。",
"unassign-entity-views-title": "确定要取消分配 { count, plural, 1 {# 个实体视图} other {# 个实体视图} }吗?",
"unassign-from-customer": "取消分配客户",
@ -1361,7 +1530,11 @@
"unable-delete-entity-alias-text": "实体别名 '{{entityAlias}}' 被以下部件使用不能删除:<br/>{{widgetsList}}",
"unable-delete-entity-alias-title": "无法删除实体别名",
"use-entity-name-filter": "用户筛选器",
"user-name-starts-with": "以 '{{prefix}}' 开头的用户"
"user-name-starts-with": "以 '{{prefix}}' 开头的用户",
"type-edge": "边缘",
"type-edges": "边缘",
"list-of-edges": "{ count, plural, 1 {1 个边缘} other {列表 # 个边缘} }",
"edge-name-starts-with": "以 '{{prefix}}' 开头的边缘"
},
"error": {
"unable-to-connect": "无法连接到服务器!请检查您的互联网连接。",
@ -1817,6 +1990,8 @@
"isgateway": "Is网关",
"label": "标签",
"name": "名称",
"routing-key": "边缘键",
"secret": "边缘密钥",
"server-attribute": "服务器属性",
"shared-attribute": "共享属性",
"timeseries": "Timeseries",
@ -2087,6 +2262,9 @@
"rulechain": {
"add": "添加规则链",
"add-rulechain-text": "添加新的规则链",
"assign-to-edge": "分配规则链给边缘",
"assign-rulechain-to-edge-title": "分配规则链给边缘",
"assign-rulechain-to-edge-text": "请选择要分配给边缘的规则链",
"copyId": "复制规则链ID",
"create-new-rulechain": "创建新的规则链",
"debug-mode": "调试模式",
@ -2098,12 +2276,15 @@
"delete-rulechains-title": "确实要删除{count, plural, 1 { 1 个规则链} other {# 个规则链} }吗?",
"description": "说明",
"details": "详情",
"edge-rulechain": "边缘规则链",
"edge-template-root": "根模版",
"events": "事件",
"export": "导出规则链",
"export-failed-error": "无法导出规则链:{{error}}",
"idCopiedMessage": "规则ID已经复制到粘贴板",
"import": "导入规则链",
"invalid-rulechain-file-error": "不能导入规则链:无效的规则链数据格式。",
"invalid-rulechain-type-error": "不能导入规则链:无效的规则链类型。期望类型为{{expectedRuleChainType}}。",
"management": "规则集管理",
"name": "名称",
"name-required": "名称必填。",
@ -2119,10 +2300,22 @@
"search": "查找规则链",
"select-rulechain": "选择规则链",
"selected-rulechains": "已选择 { count, plural, 1 {# 个规则链} other {# 个规则链} }",
"set-auto-assign-to-edge": "创建时分配规则链给边缘",
"set-auto-assign-to-edge-text": "确认后,创建时规则链将自动分配给边缘。",
"set-auto-assign-to-edge-title": "您确定创建时将规则链'{{ruleChainName}}'自动分配给边缘吗",
"set-edge-template-root-rulechain": "设置规则链为边缘模版根",
"set-edge-template-root-rulechain-text": "确认后,规则链将将会成为边缘模版根,且它会成为新创建边缘的根规则链。",
"set-edge-template-root-rulechain-title": "您确定将规则链 '{{ruleChainName}}' 设置为边缘模版根吗?",
"set-root": "设置为根规则链",
"set-root-rulechain-text": "确认之后,规则链将变为根规格链,并将处理所有传入的传输消息。",
"set-root-rulechain-title": "您确定要生成规则链'{{ruleChainName}}'根吗?",
"system": "系统"
"system": "系统",
"unassign-rulechain-from-edge-text": "确认后,规则链将会取消分配,边缘无法访问。",
"unassign-rulechains-from-edge-text": "确认后,选定的规则链将会取消分配,边缘无法访问。",
"unassign-rulechains-from-edge-title": "您确定要取消分配规则链 { count, plural, 1 {1 个规则链} other {# 个规则链} }?",
"unset-auto-assign-to-edge": "创建时分配规则链给边缘",
"unset-auto-assign-to-edge-text": "确认后,创建时规则链将不再自动分配给边缘。",
"unset-auto-assign-to-edge-title": "您确定取消创建时将规则链'{{ruleChainName}}'自动分配给边缘吗?"
},
"rulenode": {
"add": "添加规则节点",

Loading…
Cancel
Save