From 97a0a786b76693e703e1000e865e22a7b0a2c3d3 Mon Sep 17 00:00:00 2001 From: vparomskiy Date: Wed, 12 Dec 2018 19:03:17 +0200 Subject: [PATCH 1/7] aggregation for numeric data types should process both types - double and long --- .../AggregatePartitionsFunction.java | 63 +++++++++++-------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java index ac5ee640c1..b5ebb10922 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java @@ -79,7 +79,7 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct } private void processResultSetRow(Row row, AggregationResult aggResult) { - long curCount; + long curCount = 0L; Long curLValue = null; Double curDValue = null; @@ -91,14 +91,17 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct long boolCount = row.getLong(BOOL_CNT_POS); long strCount = row.getLong(STR_CNT_POS); - if (longCount > 0) { - aggResult.dataType = DataType.LONG; - curCount = longCount; - curLValue = getLongValue(row); - } else if (doubleCount > 0) { - aggResult.dataType = DataType.DOUBLE; - curCount = doubleCount; - curDValue = getDoubleValue(row); + if (longCount > 0 || doubleCount > 0) { + if (longCount > 0) { + aggResult.dataType = DataType.LONG; + curCount += longCount; + curLValue = getLongValue(row); + } + if (doubleCount > 0) { + aggResult.dataType = DataType.DOUBLE; + curCount += doubleCount; + curDValue = getDoubleValue(row); + } } else if (boolCount > 0) { aggResult.dataType = DataType.BOOLEAN; curCount = boolCount; @@ -126,16 +129,20 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct aggResult.count += curCount; if (curDValue != null) { aggResult.dValue = aggResult.dValue == null ? curDValue : aggResult.dValue + curDValue; - } else if (curLValue != null) { + } + if (curLValue != null) { aggResult.lValue = aggResult.lValue == null ? curLValue : aggResult.lValue + curLValue; } } private void processMinAggregation(AggregationResult aggResult, Long curLValue, Double curDValue, Boolean curBValue, String curSValue) { - if (curDValue != null) { - aggResult.dValue = aggResult.dValue == null ? curDValue : Math.min(aggResult.dValue, curDValue); - } else if (curLValue != null) { - aggResult.lValue = aggResult.lValue == null ? curLValue : Math.min(aggResult.lValue, curLValue); + if (curDValue != null || curLValue != null) { + if (curDValue != null) { + aggResult.dValue = aggResult.dValue == null ? curDValue : Math.min(aggResult.dValue, curDValue); + } + if (curLValue != null) { + aggResult.lValue = aggResult.lValue == null ? curLValue : Math.min(aggResult.lValue, curLValue); + } } else if (curBValue != null) { aggResult.bValue = aggResult.bValue == null ? curBValue : aggResult.bValue && curBValue; } else if (curSValue != null && (aggResult.sValue == null || curSValue.compareTo(aggResult.sValue) < 0)) { @@ -144,10 +151,13 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct } private void processMaxAggregation(AggregationResult aggResult, Long curLValue, Double curDValue, Boolean curBValue, String curSValue) { - if (curDValue != null) { - aggResult.dValue = aggResult.dValue == null ? curDValue : Math.max(aggResult.dValue, curDValue); - } else if (curLValue != null) { - aggResult.lValue = aggResult.lValue == null ? curLValue : Math.max(aggResult.lValue, curLValue); + if (curDValue != null || curLValue != null) { + if (curDValue != null) { + aggResult.dValue = aggResult.dValue == null ? curDValue : Math.max(aggResult.dValue, curDValue); + } + if (curLValue != null) { + aggResult.lValue = aggResult.lValue == null ? curLValue : Math.max(aggResult.lValue, curLValue); + } } else if (curBValue != null) { aggResult.bValue = aggResult.bValue == null ? curBValue : aggResult.bValue || curBValue; } else if (curSValue != null && (aggResult.sValue == null || curSValue.compareTo(aggResult.sValue) > 0)) { @@ -211,20 +221,19 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct private Optional processAvgOrSumResult(AggregationResult aggResult) { if (aggResult.count == 0 || (aggResult.dataType == DataType.DOUBLE && aggResult.dValue == null) || (aggResult.dataType == DataType.LONG && aggResult.lValue == null)) { return Optional.empty(); - } else if (aggResult.dataType == DataType.DOUBLE) { - return Optional.of(new BasicTsKvEntry(ts, new DoubleDataEntry(key, aggregation == Aggregation.SUM ? aggResult.dValue : (aggResult.dValue / aggResult.count)))); - } else if (aggResult.dataType == DataType.LONG) { - return Optional.of(new BasicTsKvEntry(ts, new LongDataEntry(key, aggregation == Aggregation.SUM ? aggResult.lValue : (aggResult.lValue / aggResult.count)))); + } else if (aggResult.dataType == DataType.DOUBLE || aggResult.dataType == DataType.LONG) { + double sum = Optional.ofNullable(aggResult.dValue).orElse(0.0d) + Optional.ofNullable(aggResult.lValue).orElse(0L); + return Optional.of(new BasicTsKvEntry(ts, new DoubleDataEntry(key, aggregation == Aggregation.SUM ? sum : (sum / aggResult.count)))); } return Optional.empty(); } private Optional processMinOrMaxResult(AggregationResult aggResult) { - if (aggResult.dataType == DataType.DOUBLE) { - return Optional.of(new BasicTsKvEntry(ts, new DoubleDataEntry(key, aggResult.dValue))); - } else if (aggResult.dataType == DataType.LONG) { - return Optional.of(new BasicTsKvEntry(ts, new LongDataEntry(key, aggResult.lValue))); - } else if (aggResult.dataType == DataType.STRING) { + if (aggResult.dataType == DataType.DOUBLE || aggResult.dataType == DataType.LONG) { + double currentD = aggregation == Aggregation.MIN ? Optional.ofNullable(aggResult.dValue).orElse(Double.MAX_VALUE) : Optional.ofNullable(aggResult.dValue).orElse(Double.MIN_VALUE); + double currentL = aggregation == Aggregation.MIN ? Optional.ofNullable(aggResult.lValue).orElse(Long.MAX_VALUE) : Optional.ofNullable(aggResult.lValue).orElse(Long.MIN_VALUE); + return Optional.of(new BasicTsKvEntry(ts, new DoubleDataEntry(key, aggregation == Aggregation.MIN ? Math.min(currentD, currentL) : Math.max(currentD, currentL)))); + } else if (aggResult.dataType == DataType.STRING) { return Optional.of(new BasicTsKvEntry(ts, new StringDataEntry(key, aggResult.sValue))); } else { return Optional.of(new BasicTsKvEntry(ts, new BooleanDataEntry(key, aggResult.bValue))); From 66c0e7179e5db55ea14eafa5d99dccf75520ac3f Mon Sep 17 00:00:00 2001 From: vparomskiy Date: Wed, 12 Dec 2018 19:03:17 +0200 Subject: [PATCH 2/7] aggregation for numeric data types should process both types - double and long --- .../AggregatePartitionsFunction.java | 63 +++++++++++-------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java index ac5ee640c1..b5ebb10922 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java @@ -79,7 +79,7 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct } private void processResultSetRow(Row row, AggregationResult aggResult) { - long curCount; + long curCount = 0L; Long curLValue = null; Double curDValue = null; @@ -91,14 +91,17 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct long boolCount = row.getLong(BOOL_CNT_POS); long strCount = row.getLong(STR_CNT_POS); - if (longCount > 0) { - aggResult.dataType = DataType.LONG; - curCount = longCount; - curLValue = getLongValue(row); - } else if (doubleCount > 0) { - aggResult.dataType = DataType.DOUBLE; - curCount = doubleCount; - curDValue = getDoubleValue(row); + if (longCount > 0 || doubleCount > 0) { + if (longCount > 0) { + aggResult.dataType = DataType.LONG; + curCount += longCount; + curLValue = getLongValue(row); + } + if (doubleCount > 0) { + aggResult.dataType = DataType.DOUBLE; + curCount += doubleCount; + curDValue = getDoubleValue(row); + } } else if (boolCount > 0) { aggResult.dataType = DataType.BOOLEAN; curCount = boolCount; @@ -126,16 +129,20 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct aggResult.count += curCount; if (curDValue != null) { aggResult.dValue = aggResult.dValue == null ? curDValue : aggResult.dValue + curDValue; - } else if (curLValue != null) { + } + if (curLValue != null) { aggResult.lValue = aggResult.lValue == null ? curLValue : aggResult.lValue + curLValue; } } private void processMinAggregation(AggregationResult aggResult, Long curLValue, Double curDValue, Boolean curBValue, String curSValue) { - if (curDValue != null) { - aggResult.dValue = aggResult.dValue == null ? curDValue : Math.min(aggResult.dValue, curDValue); - } else if (curLValue != null) { - aggResult.lValue = aggResult.lValue == null ? curLValue : Math.min(aggResult.lValue, curLValue); + if (curDValue != null || curLValue != null) { + if (curDValue != null) { + aggResult.dValue = aggResult.dValue == null ? curDValue : Math.min(aggResult.dValue, curDValue); + } + if (curLValue != null) { + aggResult.lValue = aggResult.lValue == null ? curLValue : Math.min(aggResult.lValue, curLValue); + } } else if (curBValue != null) { aggResult.bValue = aggResult.bValue == null ? curBValue : aggResult.bValue && curBValue; } else if (curSValue != null && (aggResult.sValue == null || curSValue.compareTo(aggResult.sValue) < 0)) { @@ -144,10 +151,13 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct } private void processMaxAggregation(AggregationResult aggResult, Long curLValue, Double curDValue, Boolean curBValue, String curSValue) { - if (curDValue != null) { - aggResult.dValue = aggResult.dValue == null ? curDValue : Math.max(aggResult.dValue, curDValue); - } else if (curLValue != null) { - aggResult.lValue = aggResult.lValue == null ? curLValue : Math.max(aggResult.lValue, curLValue); + if (curDValue != null || curLValue != null) { + if (curDValue != null) { + aggResult.dValue = aggResult.dValue == null ? curDValue : Math.max(aggResult.dValue, curDValue); + } + if (curLValue != null) { + aggResult.lValue = aggResult.lValue == null ? curLValue : Math.max(aggResult.lValue, curLValue); + } } else if (curBValue != null) { aggResult.bValue = aggResult.bValue == null ? curBValue : aggResult.bValue || curBValue; } else if (curSValue != null && (aggResult.sValue == null || curSValue.compareTo(aggResult.sValue) > 0)) { @@ -211,20 +221,19 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct private Optional processAvgOrSumResult(AggregationResult aggResult) { if (aggResult.count == 0 || (aggResult.dataType == DataType.DOUBLE && aggResult.dValue == null) || (aggResult.dataType == DataType.LONG && aggResult.lValue == null)) { return Optional.empty(); - } else if (aggResult.dataType == DataType.DOUBLE) { - return Optional.of(new BasicTsKvEntry(ts, new DoubleDataEntry(key, aggregation == Aggregation.SUM ? aggResult.dValue : (aggResult.dValue / aggResult.count)))); - } else if (aggResult.dataType == DataType.LONG) { - return Optional.of(new BasicTsKvEntry(ts, new LongDataEntry(key, aggregation == Aggregation.SUM ? aggResult.lValue : (aggResult.lValue / aggResult.count)))); + } else if (aggResult.dataType == DataType.DOUBLE || aggResult.dataType == DataType.LONG) { + double sum = Optional.ofNullable(aggResult.dValue).orElse(0.0d) + Optional.ofNullable(aggResult.lValue).orElse(0L); + return Optional.of(new BasicTsKvEntry(ts, new DoubleDataEntry(key, aggregation == Aggregation.SUM ? sum : (sum / aggResult.count)))); } return Optional.empty(); } private Optional processMinOrMaxResult(AggregationResult aggResult) { - if (aggResult.dataType == DataType.DOUBLE) { - return Optional.of(new BasicTsKvEntry(ts, new DoubleDataEntry(key, aggResult.dValue))); - } else if (aggResult.dataType == DataType.LONG) { - return Optional.of(new BasicTsKvEntry(ts, new LongDataEntry(key, aggResult.lValue))); - } else if (aggResult.dataType == DataType.STRING) { + if (aggResult.dataType == DataType.DOUBLE || aggResult.dataType == DataType.LONG) { + double currentD = aggregation == Aggregation.MIN ? Optional.ofNullable(aggResult.dValue).orElse(Double.MAX_VALUE) : Optional.ofNullable(aggResult.dValue).orElse(Double.MIN_VALUE); + double currentL = aggregation == Aggregation.MIN ? Optional.ofNullable(aggResult.lValue).orElse(Long.MAX_VALUE) : Optional.ofNullable(aggResult.lValue).orElse(Long.MIN_VALUE); + return Optional.of(new BasicTsKvEntry(ts, new DoubleDataEntry(key, aggregation == Aggregation.MIN ? Math.min(currentD, currentL) : Math.max(currentD, currentL)))); + } else if (aggResult.dataType == DataType.STRING) { return Optional.of(new BasicTsKvEntry(ts, new StringDataEntry(key, aggResult.sValue))); } else { return Optional.of(new BasicTsKvEntry(ts, new BooleanDataEntry(key, aggResult.bValue))); From 7358b4a79b540c7533c75792df01280bc1fe28c7 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Thu, 31 Jan 2019 15:30:48 +0200 Subject: [PATCH 3/7] Implemented MIN/MAX/AVG/SUM/COUNT calculation for mixed number data types --- .../server/dao/model/sql/TsKvEntity.java | 35 +++--- .../dao/sql/timeseries/TsKvRepository.java | 6 +- .../AggregatePartitionsFunction.java | 20 +++- .../server/dao/NoSqlDaoServiceTestSuite.java | 4 +- .../timeseries/BaseTimeseriesServiceTest.java | 104 +++++++++++++++++- 5 files changed, 142 insertions(+), 27 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/TsKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/TsKvEntity.java index a6d3ea6681..348f21bfdd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/TsKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/TsKvEntity.java @@ -52,22 +52,33 @@ public final class TsKvEntity implements ToData { public TsKvEntity() { } - public TsKvEntity(Double avgLongValue, Double avgDoubleValue) { - if(avgLongValue != null) { - this.longValue = avgLongValue.longValue(); + public TsKvEntity(Long longSumValue, Double doubleSumValue, Long longCountValue, Long doubleCountValue) { + double sum = 0.0; + if (longSumValue != null) { + sum += longSumValue; } - this.doubleValue = avgDoubleValue; + if (doubleSumValue != null) { + sum += doubleSumValue; + } + this.doubleValue = sum / (longCountValue + doubleCountValue); } public TsKvEntity(Long sumLongValue, Double sumDoubleValue) { - this.longValue = sumLongValue; - this.doubleValue = sumDoubleValue; + if (sumDoubleValue != null) { + this.doubleValue = sumDoubleValue + (sumLongValue != null ? sumLongValue.doubleValue() : 0.0); + } else { + this.longValue = sumLongValue; + } } - public TsKvEntity(String strValue, Long longValue, Double doubleValue) { + public TsKvEntity(String strValue, Long longValue, Double doubleValue, boolean max) { this.strValue = strValue; - this.longValue = longValue; - this.doubleValue = doubleValue; + if (longValue != null && doubleValue != null) { + this.doubleValue = max ? Math.max(doubleValue, longValue.doubleValue()) : Math.min(doubleValue, longValue.doubleValue()); + } else { + this.longValue = longValue; + this.doubleValue = doubleValue; + } } public TsKvEntity(Long booleanValueCount, Long strValueCount, Long longValueCount, Long doubleValueCount) { @@ -75,10 +86,8 @@ public final class TsKvEntity implements ToData { this.longValue = booleanValueCount; } else if (strValueCount != 0) { this.longValue = strValueCount; - } else if (longValueCount != 0) { - this.longValue = longValueCount; - } else if (doubleValueCount != 0) { - this.longValue = doubleValueCount; + } else { + this.longValue = longValueCount + doubleValueCount; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/TsKvRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/TsKvRepository.java index 296d173cf7..92bdf7e488 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/TsKvRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/TsKvRepository.java @@ -55,7 +55,7 @@ public interface TsKvRepository extends CrudRepository :startTs AND tskv.ts < :endTs") CompletableFuture findMax(@Param("entityId") String entityId, @@ -65,7 +65,7 @@ public interface TsKvRepository extends CrudRepository :startTs AND tskv.ts < :endTs") CompletableFuture findMin(@Param("entityId") String entityId, @@ -85,7 +85,7 @@ public interface TsKvRepository extends CrudRepository :startTs AND tskv.ts < :endTs") CompletableFuture findAvg(@Param("entityId") String entityId, diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java index b5ebb10922..ea1e8f1c1b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java @@ -98,6 +98,7 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct curLValue = getLongValue(row); } if (doubleCount > 0) { + aggResult.hasDouble = true; aggResult.dataType = DataType.DOUBLE; curCount += doubleCount; curDValue = getDoubleValue(row); @@ -222,17 +223,25 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct if (aggResult.count == 0 || (aggResult.dataType == DataType.DOUBLE && aggResult.dValue == null) || (aggResult.dataType == DataType.LONG && aggResult.lValue == null)) { return Optional.empty(); } else if (aggResult.dataType == DataType.DOUBLE || aggResult.dataType == DataType.LONG) { - double sum = Optional.ofNullable(aggResult.dValue).orElse(0.0d) + Optional.ofNullable(aggResult.lValue).orElse(0L); - return Optional.of(new BasicTsKvEntry(ts, new DoubleDataEntry(key, aggregation == Aggregation.SUM ? sum : (sum / aggResult.count)))); + if(aggregation == Aggregation.AVG || aggResult.hasDouble) { + double sum = Optional.ofNullable(aggResult.dValue).orElse(0.0d) + Optional.ofNullable(aggResult.lValue).orElse(0L); + return Optional.of(new BasicTsKvEntry(ts, new DoubleDataEntry(key, aggregation == Aggregation.SUM ? sum : (sum / aggResult.count)))); + } else { + return Optional.of(new BasicTsKvEntry(ts, new LongDataEntry(key, aggregation == Aggregation.SUM ? aggResult.lValue : (aggResult.lValue / aggResult.count)))); + } } return Optional.empty(); } private Optional processMinOrMaxResult(AggregationResult aggResult) { if (aggResult.dataType == DataType.DOUBLE || aggResult.dataType == DataType.LONG) { - double currentD = aggregation == Aggregation.MIN ? Optional.ofNullable(aggResult.dValue).orElse(Double.MAX_VALUE) : Optional.ofNullable(aggResult.dValue).orElse(Double.MIN_VALUE); - double currentL = aggregation == Aggregation.MIN ? Optional.ofNullable(aggResult.lValue).orElse(Long.MAX_VALUE) : Optional.ofNullable(aggResult.lValue).orElse(Long.MIN_VALUE); - return Optional.of(new BasicTsKvEntry(ts, new DoubleDataEntry(key, aggregation == Aggregation.MIN ? Math.min(currentD, currentL) : Math.max(currentD, currentL)))); + if(aggResult.hasDouble) { + double currentD = aggregation == Aggregation.MIN ? Optional.ofNullable(aggResult.dValue).orElse(Double.MAX_VALUE) : Optional.ofNullable(aggResult.dValue).orElse(Double.MIN_VALUE); + double currentL = aggregation == Aggregation.MIN ? Optional.ofNullable(aggResult.lValue).orElse(Long.MAX_VALUE) : Optional.ofNullable(aggResult.lValue).orElse(Long.MIN_VALUE); + return Optional.of(new BasicTsKvEntry(ts, new DoubleDataEntry(key, aggregation == Aggregation.MIN ? Math.min(currentD, currentL) : Math.max(currentD, currentL)))); + } else { + return Optional.of(new BasicTsKvEntry(ts, new LongDataEntry(key, aggResult.lValue))); + } } else if (aggResult.dataType == DataType.STRING) { return Optional.of(new BasicTsKvEntry(ts, new StringDataEntry(key, aggResult.sValue))); } else { @@ -247,5 +256,6 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct Double dValue = null; Long lValue = null; long count = 0; + boolean hasDouble = false; } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/NoSqlDaoServiceTestSuite.java b/dao/src/test/java/org/thingsboard/server/dao/NoSqlDaoServiceTestSuite.java index 55c2f70f1d..ddea70a38c 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/NoSqlDaoServiceTestSuite.java +++ b/dao/src/test/java/org/thingsboard/server/dao/NoSqlDaoServiceTestSuite.java @@ -25,9 +25,7 @@ import java.util.Arrays; @RunWith(ClasspathSuite.class) @ClassnameFilters({ - "org.thingsboard.server.dao.service.*ServiceNoSqlTest", - "org.thingsboard.server.dao.service.queue.cassandra.*.*.*Test", - "org.thingsboard.server.dao.service.queue.cassandra.*Test" + "org.thingsboard.server.dao.service.*ServiceNoSqlTest" }) public class NoSqlDaoServiceTestSuite { diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java index b409dea570..7378bcd380 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java @@ -221,13 +221,13 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest { 60000, 20000, 3, Aggregation.AVG))).get(); assertEquals(3, list.size()); assertEquals(10000, list.get(0).getTs()); - assertEquals(java.util.Optional.of(150L), list.get(0).getLongValue()); + assertEquals(java.util.Optional.of(150.0), list.get(0).getDoubleValue()); assertEquals(30000, list.get(1).getTs()); - assertEquals(java.util.Optional.of(350L), list.get(1).getLongValue()); + assertEquals(java.util.Optional.of(350.0), list.get(1).getDoubleValue()); assertEquals(50000, list.get(2).getTs()); - assertEquals(java.util.Optional.of(550L), list.get(2).getLongValue()); + assertEquals(java.util.Optional.of(550.0), list.get(2).getDoubleValue()); list = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0, 60000, 20000, 3, Aggregation.SUM))).get(); @@ -282,12 +282,110 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest { assertEquals(java.util.Optional.of(2L), list.get(2).getLongValue()); } + @Test + public void testFindDeviceLongAndDoubleTsData() throws Exception { + DeviceId deviceId = new DeviceId(UUIDs.timeBased()); + List entries = new ArrayList<>(); + + entries.add(save(deviceId, 5000, 100)); + entries.add(save(deviceId, 15000, 200.0)); + + entries.add(save(deviceId, 25000, 300)); + entries.add(save(deviceId, 35000, 400.0)); + + entries.add(save(deviceId, 45000, 500)); + entries.add(save(deviceId, 55000, 600.0)); + + List list = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0, + 60000, 20000, 3, Aggregation.NONE))).get(); + assertEquals(3, list.size()); + assertEquals(55000, list.get(0).getTs()); + assertEquals(java.util.Optional.of(600.0), list.get(0).getDoubleValue()); + + assertEquals(45000, list.get(1).getTs()); + assertEquals(java.util.Optional.of(500L), list.get(1).getLongValue()); + + assertEquals(35000, list.get(2).getTs()); + assertEquals(java.util.Optional.of(400.0), list.get(2).getDoubleValue()); + + list = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0, + 60000, 20000, 3, Aggregation.AVG))).get(); + assertEquals(3, list.size()); + assertEquals(10000, list.get(0).getTs()); + assertEquals(java.util.Optional.of(150.0), list.get(0).getDoubleValue()); + + assertEquals(30000, list.get(1).getTs()); + assertEquals(java.util.Optional.of(350.0), list.get(1).getDoubleValue()); + + assertEquals(50000, list.get(2).getTs()); + assertEquals(java.util.Optional.of(550.0), list.get(2).getDoubleValue()); + + list = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0, + 60000, 20000, 3, Aggregation.SUM))).get(); + + assertEquals(3, list.size()); + assertEquals(10000, list.get(0).getTs()); + assertEquals(java.util.Optional.of(300.0), list.get(0).getDoubleValue()); + + assertEquals(30000, list.get(1).getTs()); + assertEquals(java.util.Optional.of(700.0), list.get(1).getDoubleValue()); + + assertEquals(50000, list.get(2).getTs()); + assertEquals(java.util.Optional.of(1100.0), list.get(2).getDoubleValue()); + + list = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0, + 60000, 20000, 3, Aggregation.MIN))).get(); + + assertEquals(3, list.size()); + assertEquals(10000, list.get(0).getTs()); + assertEquals(java.util.Optional.of(100.0), list.get(0).getDoubleValue()); + + assertEquals(30000, list.get(1).getTs()); + assertEquals(java.util.Optional.of(300.0), list.get(1).getDoubleValue()); + + assertEquals(50000, list.get(2).getTs()); + assertEquals(java.util.Optional.of(500.0), list.get(2).getDoubleValue()); + + list = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0, + 60000, 20000, 3, Aggregation.MAX))).get(); + + assertEquals(3, list.size()); + assertEquals(10000, list.get(0).getTs()); + assertEquals(java.util.Optional.of(200.0), list.get(0).getDoubleValue()); + + assertEquals(30000, list.get(1).getTs()); + assertEquals(java.util.Optional.of(400.0), list.get(1).getDoubleValue()); + + assertEquals(50000, list.get(2).getTs()); + assertEquals(java.util.Optional.of(600.0), list.get(2).getDoubleValue()); + + list = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0, + 60000, 20000, 3, Aggregation.COUNT))).get(); + + assertEquals(3, list.size()); + assertEquals(10000, list.get(0).getTs()); + assertEquals(java.util.Optional.of(2L), list.get(0).getLongValue()); + + assertEquals(30000, list.get(1).getTs()); + assertEquals(java.util.Optional.of(2L), list.get(1).getLongValue()); + + assertEquals(50000, list.get(2).getTs()); + assertEquals(java.util.Optional.of(2L), list.get(2).getLongValue()); + } + private TsKvEntry save(DeviceId deviceId, long ts, long value) throws Exception { TsKvEntry entry = new BasicTsKvEntry(ts, new LongDataEntry(LONG_KEY, value)); tsService.save(tenantId, deviceId, entry).get(); return entry; } + private TsKvEntry save(DeviceId deviceId, long ts, double value) throws Exception { + TsKvEntry entry = new BasicTsKvEntry(ts, new DoubleDataEntry(LONG_KEY, value)); + tsService.save(tenantId, deviceId, entry).get(); + return entry; + } + + private void saveEntries(DeviceId deviceId, long ts) throws ExecutionException, InterruptedException { tsService.save(tenantId, deviceId, toTsEntry(ts, stringKvEntry)).get(); tsService.save(tenantId, deviceId, toTsEntry(ts, longKvEntry)).get(); From 094ef761cd18b5423d6b6f6ecbf441ca44d1667a Mon Sep 17 00:00:00 2001 From: vparomskiy Date: Thu, 31 Jan 2019 16:07:14 +0200 Subject: [PATCH 4/7] Merge remote-tracking branch 'upstream/master' # Conflicts: # dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java --- .../timeseries/AggregatePartitionsFunction.java | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java index 10e5c860e7..ea1e8f1c1b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java @@ -98,10 +98,7 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct curLValue = getLongValue(row); } if (doubleCount > 0) { -<<<<<<< HEAD -======= aggResult.hasDouble = true; ->>>>>>> upstream/master aggResult.dataType = DataType.DOUBLE; curCount += doubleCount; curDValue = getDoubleValue(row); @@ -226,28 +223,18 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct if (aggResult.count == 0 || (aggResult.dataType == DataType.DOUBLE && aggResult.dValue == null) || (aggResult.dataType == DataType.LONG && aggResult.lValue == null)) { return Optional.empty(); } else if (aggResult.dataType == DataType.DOUBLE || aggResult.dataType == DataType.LONG) { -<<<<<<< HEAD - double sum = Optional.ofNullable(aggResult.dValue).orElse(0.0d) + Optional.ofNullable(aggResult.lValue).orElse(0L); - return Optional.of(new BasicTsKvEntry(ts, new DoubleDataEntry(key, aggregation == Aggregation.SUM ? sum : (sum / aggResult.count)))); -======= if(aggregation == Aggregation.AVG || aggResult.hasDouble) { double sum = Optional.ofNullable(aggResult.dValue).orElse(0.0d) + Optional.ofNullable(aggResult.lValue).orElse(0L); return Optional.of(new BasicTsKvEntry(ts, new DoubleDataEntry(key, aggregation == Aggregation.SUM ? sum : (sum / aggResult.count)))); } else { return Optional.of(new BasicTsKvEntry(ts, new LongDataEntry(key, aggregation == Aggregation.SUM ? aggResult.lValue : (aggResult.lValue / aggResult.count)))); } ->>>>>>> upstream/master } return Optional.empty(); } private Optional processMinOrMaxResult(AggregationResult aggResult) { if (aggResult.dataType == DataType.DOUBLE || aggResult.dataType == DataType.LONG) { -<<<<<<< HEAD - double currentD = aggregation == Aggregation.MIN ? Optional.ofNullable(aggResult.dValue).orElse(Double.MAX_VALUE) : Optional.ofNullable(aggResult.dValue).orElse(Double.MIN_VALUE); - double currentL = aggregation == Aggregation.MIN ? Optional.ofNullable(aggResult.lValue).orElse(Long.MAX_VALUE) : Optional.ofNullable(aggResult.lValue).orElse(Long.MIN_VALUE); - return Optional.of(new BasicTsKvEntry(ts, new DoubleDataEntry(key, aggregation == Aggregation.MIN ? Math.min(currentD, currentL) : Math.max(currentD, currentL)))); -======= if(aggResult.hasDouble) { double currentD = aggregation == Aggregation.MIN ? Optional.ofNullable(aggResult.dValue).orElse(Double.MAX_VALUE) : Optional.ofNullable(aggResult.dValue).orElse(Double.MIN_VALUE); double currentL = aggregation == Aggregation.MIN ? Optional.ofNullable(aggResult.lValue).orElse(Long.MAX_VALUE) : Optional.ofNullable(aggResult.lValue).orElse(Long.MIN_VALUE); @@ -255,7 +242,6 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct } else { return Optional.of(new BasicTsKvEntry(ts, new LongDataEntry(key, aggResult.lValue))); } ->>>>>>> upstream/master } else if (aggResult.dataType == DataType.STRING) { return Optional.of(new BasicTsKvEntry(ts, new StringDataEntry(key, aggResult.sValue))); } else { From e9e10d74b89b358a028bd1a3f500928aab1c817d Mon Sep 17 00:00:00 2001 From: vparomskiy Date: Thu, 31 Jan 2019 16:09:50 +0200 Subject: [PATCH 5/7] cache webpack resources and run loaders in concurrent mode --- ui/package.json | 6 ++++-- ui/webpack.config.dev.js | 33 +++++++++++++++++++++++++++++---- ui/webpack.config.prod.js | 30 +++++++++++++++++++++++++++--- 3 files changed, 60 insertions(+), 9 deletions(-) diff --git a/ui/package.json b/ui/package.json index 24f4bf6e65..a9ec3e0d6c 100644 --- a/ui/package.json +++ b/ui/package.json @@ -11,7 +11,7 @@ ], "scripts": { "start": "babel-node --max_old_space_size=4096 server.js", - "build": "cross-env NODE_ENV=production webpack -p" + "build": "cross-env NODE_ENV=production webpack" }, "dependencies": { "@flowjs/ng-flow": "^2.7.1", @@ -136,7 +136,9 @@ "webpack-dev-middleware": "^1.6.1", "webpack-dev-server": "^1.15.1", "webpack-hot-middleware": "^2.12.2", - "webpack-material-design-icons": "^0.1.0" + "webpack-material-design-icons": "^0.1.0", + "uglifyjs-webpack-plugin": "^1.3.0", + "happypack": "^5.0.1" }, "engine": "node >= 5.9.0", "nyc": { diff --git a/ui/webpack.config.dev.js b/ui/webpack.config.dev.js index cfff811b23..bea9275315 100644 --- a/ui/webpack.config.dev.js +++ b/ui/webpack.config.dev.js @@ -18,13 +18,16 @@ const HtmlWebpackPlugin = require('html-webpack-plugin'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin'); -const StyleLintPlugin = require('stylelint-webpack-plugin') +const StyleLintPlugin = require('stylelint-webpack-plugin'); const webpack = require('webpack'); const path = require('path'); const dirTree = require('directory-tree'); const jsonminify = require("jsonminify"); +const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); +const HappyPack = require('happypack'); + const PUBLIC_RESOURCE_PATH = '/'; var langs = []; @@ -34,6 +37,9 @@ dirTree('./src/app/locale/', {extensions:/\.json$/}, (item) => { langs.push(item.name.slice(item.name.lastIndexOf('-') + 1, -5)); }); + +var happyThreadPool = HappyPack.ThreadPool({ size: 3 }); + /* devtool: 'cheap-module-eval-source-map', */ module.exports = { @@ -93,6 +99,25 @@ module.exports = { PUBLIC_PATH: JSON.stringify(PUBLIC_RESOURCE_PATH), SUPPORTED_LANGS: JSON.stringify(langs) }), + new UglifyJsPlugin({ + cache: true, + parallel: true + }), + new HappyPack({ + threadPool: happyThreadPool, + id: 'cached-babel', + loaders: ["babel-loader?cacheDirectory=true"] + }), + new HappyPack({ + threadPool: happyThreadPool, + id: 'eslint', + loaders: ["eslint-loader?{parser: 'babel-eslint'}"] + }), + new HappyPack({ + threadPool: happyThreadPool, + id: 'ng-annotate-and-cached-babel-loader', + loaders: ['ng-annotate', 'babel-loader?cacheDirectory=true'] + }) ], node: { tls: "empty", @@ -102,19 +127,19 @@ module.exports = { loaders: [ { test: /\.jsx$/, - loader: 'babel', + loader: 'happypack/loader?id=cached-babel', exclude: /node_modules/, include: __dirname, }, { test: /\.js$/, - loaders: ['ng-annotate', 'babel'], + loaders: ['happypack/loader?id=ng-annotate-and-cached-babel-loader'], exclude: /node_modules/, include: __dirname, }, { test: /\.js$/, - loader: "eslint-loader?{parser: 'babel-eslint'}", + loader: 'happypack/loader?id=eslint', exclude: /node_modules|vendor/, include: __dirname, }, diff --git a/ui/webpack.config.prod.js b/ui/webpack.config.prod.js index a442590d8b..c0809fe253 100644 --- a/ui/webpack.config.prod.js +++ b/ui/webpack.config.prod.js @@ -23,6 +23,8 @@ const webpack = require('webpack'); const path = require('path'); const dirTree = require('directory-tree'); const jsonminify = require("jsonminify"); +const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); +const HappyPack = require('happypack'); const PUBLIC_RESOURCE_PATH = '/static/'; @@ -33,6 +35,8 @@ dirTree('./src/app/locale/', {extensions:/\.json$/}, (item) => { langs.push(item.name.slice(item.name.lastIndexOf('-') + 1, -5)); }); +var happyThreadPool = HappyPack.ThreadPool({ size: 3 }); + module.exports = { devtool: 'source-map', entry: [ @@ -95,6 +99,25 @@ module.exports = { test: /\.js$|\.css$|\.svg$|\.ttf$|\.woff$|\.woff2|\.eot$\.json$/, threshold: 10240, minRatio: 0.8 + }), + new UglifyJsPlugin({ + cache: true, + parallel: true + }), + new HappyPack({ + threadPool: happyThreadPool, + id: 'cached-babel', + loaders: ["babel-loader?cacheDirectory=true"] + }), + new HappyPack({ + threadPool: happyThreadPool, + id: 'eslint', + loaders: ["eslint-loader?{parser: 'babel-eslint'}"] + }), + new HappyPack({ + threadPool: happyThreadPool, + id: 'ng-annotate-and-cached-babel-loader', + loaders: ['ng-annotate', 'babel-loader?cacheDirectory=true'] }) ], node: { @@ -105,19 +128,20 @@ module.exports = { loaders: [ { test: /\.jsx$/, - loader: 'babel', + loader: 'happypack/loader?id=cached-babel', exclude: /node_modules/, include: __dirname, }, { test: /\.js$/, - loaders: ['ng-annotate', 'babel'], + loaders: ['happypack/loader?id=ng-annotate-and-cached-babel-loader'], exclude: /node_modules/, include: __dirname, }, { test: /\.js$/, - loader: "eslint-loader?{parser: 'babel-eslint'}", + loaders: ['happypack/loader?id=eslint'], + // loader: "eslint-loader?{parser: 'babel-eslint'}", exclude: /node_modules|vendor/, include: __dirname, }, From 90c3a2b533797af7e523acb54c3c2acc59c05c9c Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 31 Jan 2019 19:38:03 +0200 Subject: [PATCH 6/7] Improve Jpa Timeseries DAO. Fixed SQL Warning Code: -1003. Issue #925, Issue #397. --- .../server/dao/model/sql/TsKvEntity.java | 67 ++++++++++------- .../dao/sql/timeseries/JpaTimeseriesDao.java | 56 +++++++++++---- .../dao/sql/timeseries/TsKvRepository.java | 70 ++++++++++++++---- .../timeseries/BaseTimeseriesServiceTest.java | 71 +++++++++++++++++-- 4 files changed, 206 insertions(+), 58 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/TsKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/TsKvEntity.java index 348f21bfdd..fe102863d3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/TsKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/TsKvEntity.java @@ -49,35 +49,52 @@ import static org.thingsboard.server.dao.model.ModelConstants.TS_COLUMN; @IdClass(TsKvCompositeKey.class) public final class TsKvEntity implements ToData { + private static final String SUM = "SUM"; + private static final String AVG = "AVG"; + private static final String MIN = "MIN"; + private static final String MAX = "MAX"; + public TsKvEntity() { } - public TsKvEntity(Long longSumValue, Double doubleSumValue, Long longCountValue, Long doubleCountValue) { - double sum = 0.0; - if (longSumValue != null) { - sum += longSumValue; - } - if (doubleSumValue != null) { - sum += doubleSumValue; - } - this.doubleValue = sum / (longCountValue + doubleCountValue); - } - - public TsKvEntity(Long sumLongValue, Double sumDoubleValue) { - if (sumDoubleValue != null) { - this.doubleValue = sumDoubleValue + (sumLongValue != null ? sumLongValue.doubleValue() : 0.0); - } else { - this.longValue = sumLongValue; - } - } - - public TsKvEntity(String strValue, Long longValue, Double doubleValue, boolean max) { + public TsKvEntity(String strValue) { this.strValue = strValue; - if (longValue != null && doubleValue != null) { - this.doubleValue = max ? Math.max(doubleValue, longValue.doubleValue()) : Math.min(doubleValue, longValue.doubleValue()); - } else { - this.longValue = longValue; - this.doubleValue = doubleValue; + } + + public TsKvEntity(Long longValue, Double doubleValue, Long longCountValue, Long doubleCountValue, String aggType) { + switch (aggType) { + case AVG: + double sum = 0.0; + if (longValue != null) { + sum += longValue; + } + if (doubleValue != null) { + sum += doubleValue; + } + long totalCount = longCountValue + doubleCountValue; + if (totalCount > 0) { + this.doubleValue = sum / (longCountValue + doubleCountValue); + } else { + this.doubleValue = 0.0; + } + break; + case SUM: + if (doubleCountValue > 0) { + this.doubleValue = doubleValue + (longValue != null ? longValue.doubleValue() : 0.0); + } else { + this.longValue = longValue; + } + break; + case MIN: + case MAX: + if (longCountValue > 0 && doubleCountValue > 0) { + this.doubleValue = MAX.equals(aggType) ? Math.max(doubleValue, longValue.doubleValue()) : Math.min(doubleValue, longValue.doubleValue()); + } else if (doubleCountValue > 0) { + this.doubleValue = doubleValue; + } else if (longCountValue > 0) { + this.longValue = longValue; + } + break; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/JpaTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/JpaTimeseriesDao.java index 312014861f..a04944e674 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/JpaTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/JpaTimeseriesDao.java @@ -161,52 +161,62 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp } private ListenableFuture> findAndAggregateAsync(EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation) { - CompletableFuture entity; + List> entitiesFutures = new ArrayList<>(); String entityIdStr = fromTimeUUID(entityId.getId()); switch (aggregation) { case AVG: - entity = tsKvRepository.findAvg( + entitiesFutures.add(tsKvRepository.findAvg( entityIdStr, entityId.getEntityType(), key, startTs, - endTs); + endTs)); break; case MAX: - entity = tsKvRepository.findMax( + entitiesFutures.add(tsKvRepository.findStringMax( entityIdStr, entityId.getEntityType(), key, startTs, - endTs); + endTs)); + entitiesFutures.add(tsKvRepository.findNumericMax( + entityIdStr, + entityId.getEntityType(), + key, + startTs, + endTs)); break; case MIN: - entity = tsKvRepository.findMin( + entitiesFutures.add(tsKvRepository.findStringMin( entityIdStr, entityId.getEntityType(), key, startTs, - endTs); - + endTs)); + entitiesFutures.add(tsKvRepository.findNumericMin( + entityIdStr, + entityId.getEntityType(), + key, + startTs, + endTs)); break; case SUM: - entity = tsKvRepository.findSum( + entitiesFutures.add(tsKvRepository.findSum( entityIdStr, entityId.getEntityType(), key, startTs, - endTs); - + endTs)); break; case COUNT: - entity = tsKvRepository.findCount( + entitiesFutures.add(tsKvRepository.findCount( entityIdStr, entityId.getEntityType(), key, startTs, - endTs); + endTs)); break; default: @@ -214,11 +224,27 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp } SettableFuture listenableFuture = SettableFuture.create(); - entity.whenComplete((tsKvEntity, throwable) -> { + + + CompletableFuture> entities = + CompletableFuture.allOf(entitiesFutures.toArray(new CompletableFuture[entitiesFutures.size()])) + .thenApply(v -> entitiesFutures.stream() + .map(CompletableFuture::join) + .collect(Collectors.toList())); + + + entities.whenComplete((tsKvEntities, throwable) -> { if (throwable != null) { listenableFuture.setException(throwable); } else { - listenableFuture.set(tsKvEntity); + TsKvEntity result = null; + for (TsKvEntity entity : tsKvEntities) { + if (entity.isNotEmpty()) { + result = entity; + break; + } + } + listenableFuture.set(result); } }); return Futures.transform(listenableFuture, new Function>() { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/TsKvRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/TsKvRepository.java index 92bdf7e488..79aa71bc29 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/TsKvRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/TsKvRepository.java @@ -35,7 +35,7 @@ public interface TsKvRepository extends CrudRepository :startTs AND tskv.ts < :endTs") + "AND tskv.ts > :startTs AND tskv.ts <= :endTs") List findAllWithLimit(@Param("entityId") String entityId, @Param("entityType") EntityType entityType, @Param("entityKey") String key, @@ -55,29 +55,63 @@ public interface TsKvRepository extends CrudRepository :startTs AND tskv.ts <= :endTs") + CompletableFuture findStringMax(@Param("entityId") String entityId, + @Param("entityType") EntityType entityType, + @Param("entityKey") String entityKey, + @Param("startTs") long startTs, + @Param("endTs") long endTs); + + @Async + @Query("SELECT new TsKvEntity(MAX(COALESCE(tskv.longValue, -9223372036854775807)), " + + "MAX(COALESCE(tskv.doubleValue, -1.79769E+308)), " + + "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + + "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + + "'MAX') FROM TsKvEntity tskv " + "WHERE tskv.entityId = :entityId AND tskv.entityType = :entityType " + - "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts < :endTs") - CompletableFuture findMax(@Param("entityId") String entityId, + "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") + CompletableFuture findNumericMax(@Param("entityId") String entityId, + @Param("entityType") EntityType entityType, + @Param("entityKey") String entityKey, + @Param("startTs") long startTs, + @Param("endTs") long endTs); + + + @Async + @Query("SELECT new TsKvEntity(MIN(tskv.strValue)) FROM TsKvEntity tskv " + + "WHERE tskv.strValue IS NOT NULL " + + "AND tskv.entityId = :entityId AND tskv.entityType = :entityType " + + "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") + CompletableFuture findStringMin(@Param("entityId") String entityId, @Param("entityType") EntityType entityType, @Param("entityKey") String entityKey, @Param("startTs") long startTs, @Param("endTs") long endTs); @Async - @Query("SELECT new TsKvEntity(MIN(tskv.strValue), MIN(tskv.longValue), MIN(tskv.doubleValue), false) FROM TsKvEntity tskv " + + @Query("SELECT new TsKvEntity(MIN(COALESCE(tskv.longValue, 9223372036854775807)), " + + "MIN(COALESCE(tskv.doubleValue, 1.79769E+308)), " + + "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + + "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + + "'MIN') FROM TsKvEntity tskv " + "WHERE tskv.entityId = :entityId AND tskv.entityType = :entityType " + - "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts < :endTs") - CompletableFuture findMin(@Param("entityId") String entityId, + "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") + CompletableFuture findNumericMin(@Param("entityId") String entityId, @Param("entityType") EntityType entityType, @Param("entityKey") String entityKey, @Param("startTs") long startTs, @Param("endTs") long endTs); @Async - @Query("SELECT new TsKvEntity(COUNT(tskv.booleanValue), COUNT(tskv.strValue), COUNT(tskv.longValue), COUNT(tskv.doubleValue)) FROM TsKvEntity tskv " + + @Query("SELECT new TsKvEntity(SUM(CASE WHEN tskv.booleanValue IS NULL THEN 0 ELSE 1 END), " + + "SUM(CASE WHEN tskv.strValue IS NULL THEN 0 ELSE 1 END), " + + "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + + "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END)) FROM TsKvEntity tskv " + "WHERE tskv.entityId = :entityId AND tskv.entityType = :entityType " + - "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts < :endTs") + "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") CompletableFuture findCount(@Param("entityId") String entityId, @Param("entityType") EntityType entityType, @Param("entityKey") String entityKey, @@ -85,23 +119,31 @@ public interface TsKvRepository extends CrudRepository :startTs AND tskv.ts < :endTs") + "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") CompletableFuture findAvg(@Param("entityId") String entityId, @Param("entityType") EntityType entityType, @Param("entityKey") String entityKey, @Param("startTs") long startTs, @Param("endTs") long endTs); - @Async - @Query("SELECT new TsKvEntity(SUM(tskv.longValue), SUM(tskv.doubleValue)) FROM TsKvEntity tskv " + + @Query("SELECT new TsKvEntity(SUM(COALESCE(tskv.longValue, 0)), " + + "SUM(COALESCE(tskv.doubleValue, 0.0)), " + + "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + + "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + + "'SUM') FROM TsKvEntity tskv " + "WHERE tskv.entityId = :entityId AND tskv.entityType = :entityType " + - "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts < :endTs") + "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") CompletableFuture findSum(@Param("entityId") String entityId, @Param("entityType") EntityType entityType, @Param("entityKey") String entityKey, @Param("startTs") long startTs, @Param("endTs") long endTs); + } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java index 7378bcd380..c2af9d6074 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java @@ -17,10 +17,7 @@ package org.thingsboard.server.dao.service.timeseries; import com.datastax.driver.core.utils.UUIDs; import lombok.extern.slf4j.Slf4j; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; +import org.junit.*; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.DeviceId; @@ -280,6 +277,66 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest { assertEquals(50000, list.get(2).getTs()); assertEquals(java.util.Optional.of(2L), list.get(2).getLongValue()); + + + entries.add(save(deviceId, 65000, "A1")); + entries.add(save(deviceId, 75000, "A2")); + entries.add(save(deviceId, 85000, "B1")); + entries.add(save(deviceId, 95000, "B2")); + entries.add(save(deviceId, 105000, "C1")); + entries.add(save(deviceId, 115000, "C2")); + + list = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 60000, + 120000, 20000, 3, Aggregation.NONE))).get(); + assertEquals(3, list.size()); + assertEquals(115000, list.get(0).getTs()); + assertEquals(java.util.Optional.of("C2"), list.get(0).getStrValue()); + + assertEquals(105000, list.get(1).getTs()); + assertEquals(java.util.Optional.of("C1"), list.get(1).getStrValue()); + + assertEquals(95000, list.get(2).getTs()); + assertEquals(java.util.Optional.of("B2"), list.get(2).getStrValue()); + + + list = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 60000, + 120000, 20000, 3, Aggregation.MIN))).get(); + + assertEquals(3, list.size()); + assertEquals(70000, list.get(0).getTs()); + assertEquals(java.util.Optional.of("A1"), list.get(0).getStrValue()); + + assertEquals(90000, list.get(1).getTs()); + assertEquals(java.util.Optional.of("B1"), list.get(1).getStrValue()); + + assertEquals(110000, list.get(2).getTs()); + assertEquals(java.util.Optional.of("C1"), list.get(2).getStrValue()); + + list = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 60000, + 120000, 20000, 3, Aggregation.MAX))).get(); + + assertEquals(3, list.size()); + assertEquals(70000, list.get(0).getTs()); + assertEquals(java.util.Optional.of("A2"), list.get(0).getStrValue()); + + assertEquals(90000, list.get(1).getTs()); + assertEquals(java.util.Optional.of("B2"), list.get(1).getStrValue()); + + assertEquals(110000, list.get(2).getTs()); + assertEquals(java.util.Optional.of("C2"), list.get(2).getStrValue()); + + list = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 60000, + 120000, 20000, 3, Aggregation.COUNT))).get(); + + assertEquals(3, list.size()); + assertEquals(70000, list.get(0).getTs()); + assertEquals(java.util.Optional.of(2L), list.get(0).getLongValue()); + + assertEquals(90000, list.get(1).getTs()); + assertEquals(java.util.Optional.of(2L), list.get(1).getLongValue()); + + assertEquals(110000, list.get(2).getTs()); + assertEquals(java.util.Optional.of(2L), list.get(2).getLongValue()); } @Test @@ -385,6 +442,12 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest { return entry; } + private TsKvEntry save(DeviceId deviceId, long ts, String value) throws Exception { + TsKvEntry entry = new BasicTsKvEntry(ts, new StringDataEntry(LONG_KEY, value)); + tsService.save(tenantId, deviceId, entry).get(); + return entry; + } + private void saveEntries(DeviceId deviceId, long ts) throws ExecutionException, InterruptedException { tsService.save(tenantId, deviceId, toTsEntry(ts, stringKvEntry)).get(); From 818e4b315abb2c837983039402dbae642ae17bc2 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 31 Jan 2019 20:30:39 +0200 Subject: [PATCH 7/7] Add Persian language. --- ui/package-lock.json | 143 ++ ui/src/app/locale/locale.constant-de_DE.json | 3 +- ui/src/app/locale/locale.constant-en_US.json | 3 +- ui/src/app/locale/locale.constant-es_ES.json | 3 +- ui/src/app/locale/locale.constant-fa_IR.json | 1582 ++++++++++++++++++ ui/src/app/locale/locale.constant-fr_FR.json | 4 +- ui/src/app/locale/locale.constant-it_IT.json | 3 +- ui/src/app/locale/locale.constant-ja_JA.json | 4 +- ui/src/app/locale/locale.constant-ko_KR.json | 3 +- ui/src/app/locale/locale.constant-ru_RU.json | 3 +- ui/src/app/locale/locale.constant-tr_TR.json | 3 +- ui/src/app/locale/locale.constant-zh_CN.json | 3 +- ui/webpack.config.dev.js | 5 - 13 files changed, 1747 insertions(+), 15 deletions(-) create mode 100644 ui/src/app/locale/locale.constant-fa_IR.json diff --git a/ui/package-lock.json b/ui/package-lock.json index 93adbd1dac..c984723fe4 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -6264,6 +6264,37 @@ "glogg": "^1.0.0" } }, + "happypack": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/happypack/-/happypack-5.0.1.tgz", + "integrity": "sha512-AzXVxLzX0mtv0T40Kic72rfcGK4Y2b/cDdtcyw+e+V/13ozl7x0+EZ4hvrL1rJ8MoefR9+FfUJQsK2irH0GWOw==", + "dev": true, + "requires": { + "async": "1.5.0", + "json-stringify-safe": "5.0.1", + "loader-utils": "1.1.0", + "serialize-error": "^2.1.0" + }, + "dependencies": { + "async": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.0.tgz", + "integrity": "sha1-J5ZkJyNXOFlWVjP8YnRES+4vjOM=", + "dev": true + }, + "loader-utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", + "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", + "dev": true, + "requires": { + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0" + } + } + } + }, "har-schema": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", @@ -12928,6 +12959,12 @@ } } }, + "serialize-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", + "integrity": "sha1-ULZ51WNc34Rme9yOWa9OW4HV9go=", + "dev": true + }, "serialize-javascript": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.5.0.tgz", @@ -15377,6 +15414,103 @@ "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", "dev": true }, + "uglifyjs-webpack-plugin": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.3.0.tgz", + "integrity": "sha512-ovHIch0AMlxjD/97j9AYovZxG5wnHOPkL7T1GKochBADp/Zwc44pEWNqpKl1Loupp1WhFg7SlYmHZRUfdAacgw==", + "dev": true, + "requires": { + "cacache": "^10.0.4", + "find-cache-dir": "^1.0.0", + "schema-utils": "^0.4.5", + "serialize-javascript": "^1.4.0", + "source-map": "^0.6.1", + "uglify-es": "^3.3.4", + "webpack-sources": "^1.1.0", + "worker-farm": "^1.5.2" + }, + "dependencies": { + "ajv": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.7.0.tgz", + "integrity": "sha512-RZXPviBTtfmtka9n9sy1N5M5b82CbxWIR6HIis4s3WQTXDJamc/0gpCWNGz6EWdWp4DOfjzJfhz/AS9zVPjjWg==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.3.0.tgz", + "integrity": "sha512-CMzN9S62ZOO4sA/mJZIO4S++ZM7KFWzH3PPWkveLhy4OZ9i1/VatgwWMD46w/XbGCBy7Ye0gCk+Za6mmyfKK7g==", + "dev": true + }, + "commander": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", + "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", + "dev": true + }, + "find-cache-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", + "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^1.0.0", + "pkg-dir": "^2.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + }, + "schema-utils": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", + "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "uglify-es": { + "version": "3.3.9", + "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", + "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==", + "dev": true, + "requires": { + "commander": "~2.13.0", + "source-map": "~0.6.1" + } + } + } + }, "ultron": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz", @@ -16256,6 +16390,15 @@ "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", "dev": true }, + "worker-farm": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.6.0.tgz", + "integrity": "sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ==", + "dev": true, + "requires": { + "errno": "~0.1.7" + } + }, "wrap-ansi": { "version": "2.1.0", "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", diff --git a/ui/src/app/locale/locale.constant-de_DE.json b/ui/src/app/locale/locale.constant-de_DE.json index b777b18b0f..dd462a383f 100644 --- a/ui/src/app/locale/locale.constant-de_DE.json +++ b/ui/src/app/locale/locale.constant-de_DE.json @@ -1575,7 +1575,8 @@ "ru_RU": "Russisch", "es_ES": "Spanisch", "ja_JA": "Japanisch", - "tr_TR": "Türkisch" + "tr_TR": "Türkisch", + "fa_IR": "Persisch" } } } \ No newline at end of file diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index 4f12ee056e..eba880d826 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -1575,7 +1575,8 @@ "ru_RU": "Russian", "es_ES": "Spanish", "ja_JA": "Japanese", - "tr_TR": "Turkish" + "tr_TR": "Turkish", + "fa_IR": "Persian" } } } \ No newline at end of file diff --git a/ui/src/app/locale/locale.constant-es_ES.json b/ui/src/app/locale/locale.constant-es_ES.json index 8190157e7a..afb96ceb0c 100644 --- a/ui/src/app/locale/locale.constant-es_ES.json +++ b/ui/src/app/locale/locale.constant-es_ES.json @@ -1575,7 +1575,8 @@ "ru_RU": "Ruso", "es_ES": "Español", "ja_JA": "Japonés", - "tr_TR": "Turco" + "tr_TR": "Turco", + "fa_IR": "Persa" } } } \ No newline at end of file diff --git a/ui/src/app/locale/locale.constant-fa_IR.json b/ui/src/app/locale/locale.constant-fa_IR.json new file mode 100644 index 0000000000..de9b2a4f5e --- /dev/null +++ b/ui/src/app/locale/locale.constant-fa_IR.json @@ -0,0 +1,1582 @@ +{ + "access": { + "unauthorized": "غير مجاز", + "unauthorized-access": "دسترسي غير مجاز", + "unauthorized-access-text": "!شما بايد وارد شويد تا به اين منبع دسترسي پيدا کنيد", + "access-forbidden": "دسترسي ممنوع", + "access-forbidden-text": "!اگر هنوز تمايل داريد به اينجا دسترسي پيدا کنيد، تلاش کنيد با نام کاربري ديگري وارد شويد
.شما حق دسترسي به اينجا را نداريد", + "refresh-token-expired": "اين بخش، منقضي شده است", + "refresh-token-failed": "بازيابي اين بخش ممکن نيست" + }, + "action": { + "activate": "فعال سازي", + "suspend": "معلّق", + "save": "ذخيره سازي", + "saveAs": "ذخيره سازي در", + "cancel": "لغو", + "ok": "قبول", + "delete": "حذف", + "add": "اضافه", + "yes": "بله", + "no": "خير", + "update": "به روز کردن", + "remove": "حذف", + "search": "جستجو", + "clear-search": "پاک کردن جستجو", + "assign": "تخصيص", + "unassign": "لغو تخصيص", + "share": "به اشتراک گذاري", + "make-private": "شخصي سازي", + "apply": "اعمال", + "apply-changes": "اعمال تغييرات", + "edit-mode": "حالت ويرايش", + "enter-edit-mode": "ورود به حالت ويرايش", + "decline-changes": "عدم پذيرش تغييرات", + "close": "بستن", + "back": "بازگشت", + "run": "اجرا", + "sign-in": "!ورود", + "edit": "ويرايش", + "view": "نمايش", + "create": "ايجاد", + "drag": "کشيدن", + "refresh": "بازيابي", + "undo": "برگرداندن آخرين عمل", + "copy": "رونوشت", + "paste": "الصاق رونوشت", + "copy-reference": "رونوشت مرجع", + "paste-reference": "رونوشت مرجع", + "import": "وارد کردن", + "export": "صدور", + "share-via": "{{provider}} اشتراک گذاري از طريق" + }, + "aggregation": { + "aggregation": "تجميع", + "function": "تابع تجميع داده ها", + "limit": "بيشترين مقادير", + "group-interval": "فاصله گروه بندي", + "min": "کمترين", + "max": "بيشترين", + "avg": "ميانگين", + "sum": "جمع", + "count": "شمارش", + "none": "هيچکدام" + }, + "admin": { + "general": "عمومي", + "general-settings": "تنظيمات عمومي", + "outgoing-mail": "پيام خروجي", + "outgoing-mail-settings": "تنظيمات پيام خروجي", + "system-settings": "تنظيمات سيستم", + "test-mail-sent": "!ارسال پيام آزمايشي موفقيت آميز بود", + "base-url": "مبنا URL", + "base-url-required": ".مبنا مورد نياز است URL", + "mail-from": "... پيام از", + "mail-from-required": ".پيام از ... مورد نياز است", + "smtp-protocol": "SMTP قرارداد", + "smtp-host": "SMTP ميزبان", + "smtp-host-required": ".مورد نياز است SMTP ميزبان", + "smtp-port": "SMTP درگاه", + "smtp-port-required": ".فراهم کنيد SMTP شما بايد يک درگاه", + "smtp-port-invalid": ".معتبر باشد SMTP به نظر نمي آيد يک درگاه", + "timeout-msec": "مهلت (msec)", + "timeout-required": ".مهلت مورد نياز است", + "timeout-invalid": ".مهلت، به نظر نمي آيد معتبر باشد", + "enable-tls": "TLS فعال سازي", + "send-test-mail": "ارسال پيام آزمايشي" + }, + "alarm": { + "alarm": "هشدار", + "alarms": "هشدارها", + "select-alarm": "انتخاب هشدار", + "no-alarms-matching": ".يافت نشد '{{entity}}' هيچ هشداري مطابق", + "alarm-required": "هشدار مورد نياز است", + "alarm-status": "وضعيت هشدار", + "search-status": { + "ANY": "هر", + "ACTIVE": "فعال", + "CLEARED": "پاک شده", + "ACK": "تصديق شده", + "UNACK": "تصديق نشده" + }, + "display-status": { + "ACTIVE_UNACK": "تصديق نشده فعال", + "ACTIVE_ACK": "تصديق شده فعال", + "CLEARED_UNACK": "تصديق نشده پاک شده", + "CLEARED_ACK": "تصديق شده پاک شده" + }, + "no-alarms-prompt": "هيچ هشداري يافت نشد", + "created-time": "زمان ايجاد", + "type": "نوع", + "severity": "شدت", + "originator": "مبدأ", + "originator-type": "نوع مبدأ", + "details": "جزئيات", + "status": "وضعيت", + "alarm-details": "جزئيات هشدار", + "start-time": "زمان شروع", + "end-time": "زمان پايان", + "ack-time": "زمان تصديق", + "clear-time": "زمان پاک شدن", + "severity-critical": "بحراني", + "severity-major": "مهم", + "severity-minor": "جزئي", + "severity-warning": "اخطار", + "severity-indeterminate": "نامشخص", + "acknowledge": "تصديق", + "clear": "پاک کردن", + "search": "جستجوي هشدارها", + "selected-alarms": "اننخاب شده { count, plural, 1 {1 هشدار} other {# هشدارها} }", + "no-data": "هيچ داده اي براي نمايش نيست", + "polling-interval": "هشدار دهنده فاصله نمونه برداري (sec)", + "polling-interval-required": ".هشدار دهنده فاصله نمونه برداري مورد نياز است", + "min-polling-interval-message": ".حداقل فاصله مجاز نمونه برداري، 1 ثانيه است", + "aknowledge-alarms-title": "{ count, plural, 1 {1 هشدار} other {# هشدارها} } تصديق", + "aknowledge-alarms-text": "اطمينان داريد؟ { count, plural, 1 {1 هشدار} other {# هشدارها} } آيا شما از تصديق", + "aknowledge-alarm-title": "تصديق هشدار", + "aknowledge-alarm-text": "آيا شما از تصديق هشدار اطمينان داريد؟", + "clear-alarms-title": "{ count, plural, 1 {1 هشدار} other {# هشدارها} } پاک کردن", + "clear-alarms-text": "اطمينان داريد؟ { count, plural, 1 {1 هشدار} other {# هشدارها} } آيا شما از پاک کردن", + "clear-alarm-title": "پاک کردن هشدار", + "clear-alarm-text": "آيا شما از پاک کردن هشدار اطمينان داريد؟", + "alarm-status-filter": "فيلتر وضعيت هشدار" + }, + "alias": { + "add": "افزودن نام مستعار", + "edit": "ويرايش نام مستعار", + "name": "نام مستعار", + "name-required": "نام مستعار مورد نياز است", + "duplicate-alias": ".در حال حاضر نام مستعار مشابهي وجود دارد", + "filter-type-single-entity": "موجودي تکي", + "filter-type-entity-list": "ليست موجودي", + "filter-type-entity-name": "نام موجودي", + "filter-type-state-entity": "موجودي از وضعيت داشبورد", + "filter-type-state-entity-description": "پارامترهاي موجودي گرفته شده از وضعيت داشبورد", + "filter-type-asset-type": "نوع دارايي", + "filter-type-asset-type-description": "'{{assetType}}' دارايي هاي نوع", + "filter-type-asset-type-and-name-description": ".شروع مي شود '{{prefix}}' که نامشان با '{{assetType}}' دارايي هاي نوع", + "filter-type-device-type": "نوع دستگاه", + "filter-type-device-type-description": "'{{deviceType}}' دستگاه هاي نوع", + "filter-type-device-type-and-name-description": ".شروع مي شود '{{prefix}}' که نامشان با '{{deviceType}}' دستگاه هاي نوع", + "filter-type-entity-view-type": "نوع نمايش موجودي", + "filter-type-entity-view-type-description": "'{{entityView}}' نمايش هاي موجودي نوع ", + "filter-type-entity-view-type-and-name-description": ".شروع مي شود '{{prefix}}' که نامشان با '{{entityView}}' نمايش هاي موجودي نوع", + "filter-type-relations-query": "پرس و جو درمورد ارتباطات", + "filter-type-relations-query-description": ". دارند {{direction}} {{rootEntity}} را {{relationType}} که ارتباط {{entities}}", + "filter-type-asset-search-query": "پرس و جو درمورد جستجوي دارايي", + "filter-type-asset-search-query-description": ".دارند {{direction}} {{rootEntity}} را {{relationType}} که ارتباط{{assetTypes}} دارايي ها از انواع", + "filter-type-device-search-query": "پرس و چو درمورد جستجوي دستگاه", + "filter-type-device-search-query-description": ".دارند {{direction}} {{rootEntity}} را {{relationType}} که ارتباط{{deviceTypes}} دستگاه ها از انواع", + "filter-type-entity-view-search-query": "پرس و جو درمورد جستجوي نمايش موجودي", + "filter-type-entity-view-search-query-description": ".دارند {{direction}} {{rootEntity}} را {{relationType}} که ارتباط{{entityViewTypes}} نمايش هاي موجودي از انواع", + "entity-filter": "فيلتر موجودي", + "resolve-multiple": "تصميم با توجه به موجودي هاي متعدد", + "filter-type": "نوع فيلتر", + "filter-type-required": ".نوع فيلتر مورد نياز است", + "entity-filter-no-entity-matched": ".هيچ موجودي منطبق بر فيلتر مشخص شده يافت نشد", + "no-entity-filter-specified": ".هيچ فيلتر موجودي اي تعيين نشده است", + "root-state-entity": "موجودي وضعيت داشبورد به عنوان پايه استفاده شود", + "root-entity": "موجودي پايه", + "state-entity-parameter-name": "نام پارامتر موجودي وضعيت", + "default-state-entity": "موجودي وضعيت پيش فرض", + "default-entity-parameter-name": "به صورت پيش فرض", + "max-relation-level": "بالاترين سطح ارتباط", + "unlimited-level": "سطح نامحدود", + "state-entity": "موجودي وضعيت داشبورد", + "all-entities": "تمام موجودي ها", + "any-relation": "هر" + }, + "asset": { + "asset": "دارايي", + "assets": "دارايي ها", + "management": "مديريت دارايي", + "view-assets": "نمايش دارايي ها", + "add": "افزودن دارايي", + "assign-to-customer": "تخصيص به مشتري", + "assign-asset-to-customer": "تخصيص دارايي(ها) به مشتري", + "assign-asset-to-customer-text": "لطفا دارايي ها را انتخاب کنيد تا به مشتري تخصيص يابد", + "no-assets-text": "هيچ دارايي اي يافت نشد", + "assign-to-customer-text": "لطفا مشتري را انتخاب کنيد تا دارايي(ها) تخصيص يابد", + "public": "عمومي", + "assignedToCustomer": "تخصيص يافته به مشتري", + "make-public": "عمومي سازي دارايي", + "make-private": "شخصي سازي دارايي", + "unassign-from-customer": "لغو تخصيص از مشتري", + "delete": "حذف دارايي", + "asset-public": "دارايي عمومي است", + "asset-type": "نوع دارايي", + "asset-type-required": ".نوع دارايي مورد نياز است", + "select-asset-type": "انتخاب کردن نوع دارايي", + "enter-asset-type": "وارد کردن نوع دارايي", + "any-asset": "هر دارايي", + "no-asset-types-matching": ".يافت نشد '{{entitySubtype}}' هيچ دارايي منطبق بر", + "asset-type-list-empty": ".هيچيک از انواع دارايي انتخاب نشد", + "asset-types": "انواع دارايي", + "name": "نام", + "name-required": ".نام مورد نياز است", + "description": "توصيف", + "type": "نوع", + "type-required": ".نوع مورد نياز است", + "details": "جزئيات", + "events": "رويدادها", + "add-asset-text": "افزودن دارايي جديد", + "asset-details": "جزئيات دارايي", + "assign-assets": "تخصيص دارايي ها", + "assign-assets-text": "به مشتري { count, plural, 1 {1 دارايي} other {# دارايي} } تخصيص", + "delete-assets": "حذف دارايي ها", + "unassign-assets": "لغو تخصيص دارايي ها", + "unassign-assets-action-title": "از مشتري { count, plural, 1 {1 دارايي} other {# دارايي} } لغو تخصيص", + "assign-new-asset": "تخصيص دارايي جديد", + "delete-asset-title": "مطمئنيد؟ '{{assetName}}' آيا از حذف دارايي", + "delete-asset-text": ".مراقب باشيد، پس از تأييد، دارايي و تمام داده هاي مربوطه غير قابل بازيابي مي شوند", + "delete-assets-title": "مطمئنيد؟ { count, plural, 1 {1 دارايي} other {# دارايي} } آيا از حذف", + "delete-assets-action-title": "{ count, plural, 1 {1 دارايي} other {# دارايي} } حذف", + "delete-assets-text": ".مراقب باشيد، پس از تأييد، تمام دارايي هاي انتخاب شده حذف، و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند", + "make-public-asset-title": "مطمئنيد؟ '{{assetName}}' آيا از عمومي سازي", + "make-public-asset-text": ".پس از تأييد، دارايي و تمامي داده هايش عمومي و قابل دسترسي براي ديگران مي شود", + "make-private-asset-title": "مطمئنيد؟ '{{assetName}}' آيا از شخصي سازي دارايي", + "make-private-asset-text": ".پس از تأييد، دارايي و تمامي داده هايش شخصي و خارج از دسترس ديگران مي شوند", + "unassign-asset-title": "مطمئنيد؟ '{{assetName}}' آيا از لغو تخصيص دارايي", + "unassign-asset-text": ".پس از تأييد، دارايي، لغو تخصيص و خارج از دسترس مشتري مي شود", + "unassign-asset": "لغو تخصيص دارايي", + "unassign-assets-title": "مطمئنيد؟ { count, plural, 1 {1 دارايي} other {# دارايي} } آيا از لغو تخصيص", + "unassign-assets-text": ".پس از تأييد، تمام دارايي هاي انتخاب شده، لغو تخصيص و خارج از دسترس مشتري مي شوند", + "copyId": "دارايي ID رونوشت از", + "idCopiedMessage": "دارايي در حافظه موقت رونوشت شد ID", + "select-asset": "انتخاب دارايي", + "no-assets-matching": ".يافت نشد '{{entity}}' هيچ دارايي منطبق بر", + "asset-required": "دارايي مود نياز است", + "name-starts-with": "نام دارايي شروع مي شود با" + }, + "attribute": { + "attributes": "ويژگي ها", + "latest-telemetry": "آخرين سنجش", + "attributes-scope": "حوزه ويژگي هاي موجودي", + "scope-latest-telemetry": "آخرين سنجش", + "scope-client": "ويژگي هاي مشتري", + "scope-server": "ويژگي هاي سِروِر", + "scope-shared": "ويژگي هاي مشترک", + "add": "افزودن ويژگي ها", + "key": "کليد", + "last-update-time": "آخرين زمان به روز رساني", + "key-required": ".کليد ويژگي مورد نياز است", + "value": "مقدار", + "value-required": ".مقدار ويژگي مورد نياز است", + "delete-attributes-title": "مطمئنيد؟ { count, plural, 1 {1 ويژگي} other {# ويژگي} } آيا از حذف", + "delete-attributes-text": ".مراقب باشيد، پس از تأييد، تمام ويژگي هاي انتخاب شده حذف مي گردند", + "delete-attributes": "حذف ويژگي ها", + "enter-attribute-value": "وارد کردن مقدار ويژگي", + "show-on-widget": "نمايش بر ويجت", + "widget-mode": "حالت ويجت", + "next-widget": "ويجت بعد", + "prev-widget": "ويجت قبل", + "add-to-dashboard": "افزودن به داشبورد", + "add-widget-to-dashboard": "افزودن ويجت به داشبورد", + "selected-attributes": "انتخاب شدند { count, plural, 1 {1 ويژگي} other {# ويژگي} }", + "selected-telemetry": "انتخاب شد { count, plural, 1 {1 واحد سنجش} other {# واحد سنجش} }" + }, + "audit-log": { + "audit": "بازبيني", + "audit-logs": "داده هاي ثبت شده از بازبيني", + "timestamp": "برچسب زمان", + "entity-type": "نوع موحودي", + "entity-name": "نام موجودي", + "user": "کاربر", + "type": "نوع", + "status": "وضعيت", + "details": "جزئيات", + "type-added": "اضافه شده", + "type-deleted": "حذف شده", + "type-updated": "به روز", + "type-attributes-updated": "ويژگي ها به روز شد", + "type-attributes-deleted": "ويژگي ها حذف شد", + "type-rpc-call": "RPC فراخواني", + "type-credentials-updated": "اعتبارنامه ها به روز شد", + "type-assigned-to-customer": "به مشتري تخصيص يافت", + "type-unassigned-from-customer": "از مشتري لغو تخصيص شد", + "type-activated": "فعال شد", + "type-suspended": "معلق", + "type-credentials-read": "اعتبارنامه ها خوانده شد", + "type-attributes-read": "ويژگي ها خوانده شد", + "type-relation-add-or-update": "ارتباط به روز شد", + "type-relation-delete": "ارتباط حذف شد", + "type-relations-delete": "تمام ارتباطات حذف شد", + "type-alarm-ack": "تصديق شده", + "type-alarm-clear": "پاک شده", + "status-success": "موفقيت", + "status-failure": "عدم موفقيت", + "audit-log-details": "بازبيني جزئيات ثبت داده ها", + "no-audit-logs-prompt": "هيچ داده ثبت شده اي يافت نشد", + "action-data": "داده هاي اقدام", + "failure-details": "جزئيات عدم موفقيت", + "search": "جستجوي داده هاي ثبت شده از بازبيني", + "clear-search": "پاک کردن جستجو" + }, + "confirm-on-exit": { + "message": "شما تغييراتي ذخيره نشده داريد. از ترک اين صفحه مطمئنيد؟", + "html-message": "از ترک اين صفحه مطمئنيد؟
.شما تغييراتي ذخيره نشده داريد", + "title": "تغييرات ذخيره نشده " + }, + "contact": { + "country": "کشور", + "city": "شهر", + "state": "استان / ايالت", + "postal-code": "کد پستي", + "postal-code-invalid": ".قالب کد پستي نامعتبر است", + "address": "نشاني", + "address2": "2 نشاني", + "phone": "تلفن", + "email": "پست الکترونيک", + "no-address": "بدون آدرس" + }, + "common": { + "username": "نام کاربري", + "password": "رمز عبور", + "enter-username": "وارد کردن نام کاربري", + "enter-password": "وارد کردن رمز عبور", + "enter-search": "وارد کردن جستجو" + }, + "content-type": { + "json": "JSON", + "text": "Text", + "binary": "Binary (Base64)" + }, + "customer": { + "customer": "مشتري", + "customers": "مشتريان", + "management": "مديريت مشتري", + "dashboard": "داشبورد مشتري", + "dashboards": "داشبوردهاي مشتري", + "devices": "دستگاه هاي مشتري", + "entity-views": "نمايش موجودي مشتري", + "assets": "دارايي هاي مشتري", + "public-dashboards": "داشبوردهاي عمومي", + "public-devices": "دستگاه هاي عمومي", + "public-assets": "دارايي هاي عمومي", + "public-entity-views": "نمايش موجودي عمومي", + "add": "افزودن مشتري", + "delete": "حذف مشتري", + "manage-customer-users": "مديريت کاربرهاي مشتري", + "manage-customer-devices": "مديريت دستگاه هاي مشتري", + "manage-customer-dashboards": "مديريت داشبوردهاي مشتري", + "manage-public-devices": "مديريت دستگاه هاي عمومي", + "manage-public-dashboards": "مديريت داشبوردهاي عمومي", + "manage-customer-assets": "مديريت دارايي هاي مشتري", + "manage-public-assets": "مديريت دارايي هاي عمومي", + "add-customer-text": "افزودن مشتري جديد", + "no-customers-text": "هيچ مشتري اي يافت نشد", + "customer-details": "جزئيات اطلاعات مشتري", + "delete-customer-title": "مطمئنيد؟ '{{customerTitle}}' از حذف مشتري", + "delete-customer-text": ".مراقب باشيد، پس از تأييد، مشتري و تمامي داده هاي مربوطه، غير قابل بازيابي مي شوند", + "delete-customers-title": "مطمئنيد؟ { count, plural, 1 {1 مشتري} other {# مشتري} } از حذف", + "delete-customers-action-title": "{ count, plural, 1 {1 مشتري} other {# مشتري} } حذف", + "delete-customers-text": ".مراقب باشيد، پس از تأييد، تمام مشتريانِ انتخاب شده حذف، و تمامي داده هاي مربوطه غير قابل دسترسي مي شوند", + "manage-users": "مديريت کاربرها", + "manage-assets": "مديريت دارايي ها", + "manage-devices": "مديريت دستگاه ها", + "manage-dashboards": "مديريت داشبوردها", + "title": "عنوان", + "title-required": ".عنوان مورد نياز است", + "description": "توصيف", + "details": "جزئيات", + "events": "رويدادها", + "copyId": "مشتري ID رونوشت از", + "idCopiedMessage": "مشتري در حافظه موقت رونوشت شد ID", + "select-customer": "انتخاب مشتري", + "no-customers-matching": ".يافت نشد '{{entity}}' هيچ مشتري منطبق بر", + "customer-required": "مشتري مورد نياز است", + "select-default-customer": "انتخاب مشتري پيش فرض", + "default-customer": "مشتري پيش فرض", + "default-customer-required": "جهت عيب يابي داشبورد در سطح کاربر مياني، مشتري پيش فرض مورد نياز است" + }, + "datetime": { + "date-from": "تاريخ از", + "time-from": "زمان از", + "date-to": "تاريخ تا", + "time-to": "زمان تا" + }, + "dashboard": { + "dashboard": "داشبورد", + "dashboards": "داشبوردها", + "management": "مديريت داشبورد", + "view-dashboards": "نمايش داشبوردها", + "add": "افزودن داشبورد", + "assign-dashboard-to-customer": "تخصيص داشبورد(ها) به مشتري", + "assign-dashboard-to-customer-text": "لطفا داشبوردها را، جهت تخصيص به مشتري، انتخاب کنيد", + "assign-to-customer-text": "لطفا مشتري را، جهت تخصيص داشبورد(ها)، انتخاب کنيد", + "assign-to-customer": "تخصيص به مشتري", + "unassign-from-customer": "لغو تخصيص از مشتري", + "make-public": "عمومي سازي مشتري", + "make-private": "شخصي سازي داشبورد", + "manage-assigned-customers": "مديريت مشتريان تخصيص داده شده", + "assigned-customers": "مشتريان تخصيص داده شده", + "assign-to-customers": "تخصيص داشبورد(ها) به مشتريان", + "assign-to-customers-text": "لطفا مشتريان را، جهت تخصيص داشبورد(ها)، انتخاب کنيد", + "unassign-from-customers": "لغو تخصيص داشبوردها از مشتريان", + "unassign-from-customers-text": "لطفا مشتريان را، جهت لغو تخصيص از داشبورد(ها)، انتخاب کنيد", + "no-dashboards-text": "هيچ داشبوردي يافت نشد", + "no-widgets": "هيچ ويجتي پيکربندي نشده است", + "add-widget": "افزودن ويجت جديد", + "title": "عنوان", + "select-widget-title": "انتخاب ويجت", + "select-widget-subtitle": "ليست انواع ويجت هاي در دسترس", + "delete": "حذف داشبورد", + "title-required": ".عنوان مورد نياز است", + "description": "توصيف", + "details": "جزئيات", + "dashboard-details": "جزئيات داشبورد", + "add-dashboard-text": "افزودن داشبورد جديد", + "assign-dashboards": "تخصيص داشبوردها", + "assign-new-dashboard": "تخصيص داشبورد جديد", + "assign-dashboards-text": "به مشتريان { count, plural, 1 {1 داشبورد} other {# داشبورد} } تخصيص", + "unassign-dashboards-action-text": "از مشتريان { count, plural, 1 {1 داشبورد} other {# داشبورد} } لغو تخصيص", + "delete-dashboards": "حذف داشبوردها", + "unassign-dashboards": "لغو تخصيص داشبوردها", + "unassign-dashboards-action-title": "از مشتري { count, plural, 1 {1 داشبورد} other {# داشبورد} } لغو تخصيص", + "delete-dashboard-title": "مطمئنيد؟ '{{dashboardTitle}}' از حذف", + "delete-dashboard-text": ".مراقب باشيد، پس از تأييد، داشبورد و تمامي داده هاي مربوطه، غير قابل بازيابي مي شوند", + "delete-dashboards-title": "مطمئنيد؟ { count, plural, 1 {1 داشبورد} other {# داشبورد} } از حذف", + "delete-dashboards-action-title": "{ count, plural, 1 {1 داشبورد} other {# داشبورد} } حذف", + "delete-dashboards-text": ".مراقب باشيد، پس از تأييد، تمام داشبوردهاي انتخاب شده حذف، و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند ", + "unassign-dashboard-title": "مطمئنيد؟ '{{dashboardTitle}}' از لغو تخصيص داشبورد", + "unassign-dashboard-text": ".پس از تأييد، داشبورد، لغو تخصيص و خارج از دسترس مشتري مي شود", + "unassign-dashboard": "لغو تخصيص داشبورد", + "unassign-dashboards-title": "مطمئنيد؟ { count, plural, 1 {1 داشبورد} other {# داشبورد} } از لغو تخصيص", + "unassign-dashboards-text": ".پس از تأييد، تمام داشبوردهاي انتخاب شده، لغو تخصيص و خارج از دسترس مشتري مي شوند", + "public-dashboard-title": "داشبورد اکنون عمومي است", + "public-dashboard-text": ":قابل دسترسي است اکنون عمومي بوده و از طريق پيوند عمومي ديگر ، {{dashboardTitle}} ،داشبورد شما", + "public-dashboard-notice": ".فراموش نکنيد براي دسترسي به داده هاي دستگاه هاي مربوطه، آنها را عمومي نماييد :توجه", + "make-private-dashboard-title": "مطمئنيد؟ '{{dashboardTitle}}' از شخصي سازي داشبورد", + "make-private-dashboard-text": ".پس از تأييد، داشبورد، شخصي و خارج از دسترس ديگران مي شود", + "make-private-dashboard": "شخصي سازي داشبورد", + "socialshare-text": "ThingsBoard طراحي شده توسط '{{dashboardTitle}}'", + "socialshare-title": "ThingsBoard طراحي شده توسط '{{dashboardTitle}}'", + "select-dashboard": "انتخاب داشبورد", + "no-dashboards-matching": ".يافت نشد '{{entity}}' هيچ داشبوردي منطبق بر", + "dashboard-required": ".داشبورد مورد نياز است", + "select-existing": "انتخاب داشبورد موجود", + "create-new": "ايجاد داشبورد جديد", + "new-dashboard-title": "عنوان داشبورد جديد", + "open-dashboard": "باز کردن داشبورد", + "set-background": "تنظيم پس زمينه", + "background-color": "رنگ پس زمينه", + "background-image": "تصوير پس زمينه", + "background-size-mode": "حالت اندازه پس زمينه", + "no-image": "هيچ تصويري انتخاب نشد", + "drop-image": ".جهت بارگذاري يک تصوير، آن را با موس کِشيده و رها کنيد، و يا روي آن کليک نماييد", + "settings": "تنظيمات", + "columns-count": "شمارش ستون ها", + "columns-count-required": ".شمارش ستون ها مورد نياز است", + "min-columns-count-message": ".کمترين تعداد مجاز ستون ها 10 عدد است", + "max-columns-count-message": ".بيشترين تعداد مجاز ستون ها 1000 عدد است", + "widgets-margins": "حاشيه بين ويجت ها", + "horizontal-margin": "حاشيه افقي", + "horizontal-margin-required": ".مقدار حاشيه افقي مورد نياز است", + "min-horizontal-margin-message": ".کمترين مقدار مجاز حاشيه افقي 0 است", + "max-horizontal-margin-message": ".بيشترين مقدار مجاز حاشيه افقي 50 است", + "vertical-margin": "حاشيه عمودي", + "vertical-margin-required": ".حاشيه عمودي مورد نياز است", + "min-vertical-margin-message": ".کمترين مقدار مجاز حاشيه عمودي 0 است", + "max-vertical-margin-message": ".بيشترين مقدار مجاز حاشيه افقي 50 است", + "autofill-height": "تنظيم خودکار ارتفاع چيدمان طرح", + "mobile-layout": "تنظيمات چيدمان طرح در تلفن همراه", + "mobile-row-height": "(px) ارتفاع رديف در تلفن همراه", + "mobile-row-height-required": ".مقدار ارتفاع ردبف در تلفن همراه مورد نياز است", + "min-mobile-row-height-message": ".کمترين مقدار مجاز ارتفاع رديف در تلفن همراه 5 پيکسل است", + "max-mobile-row-height-message": ".بيشترين مقدار مجاز ارتفاع رديف در تلفن همراه 200 پيکسل است", + "display-title": "نمايش عنوان داشبورد", + "toolbar-always-open": "باز نگه داشتن نوار ابزار", + "title-color": "رنگ عنوان", + "display-dashboards-selection": "نمايش انتخاب داشبوردها", + "display-entities-selection": "نمايش انتخاب موجودي ها", + "display-dashboard-timewindow": "نمايش پنجره زمان", + "display-dashboard-export": "نمايش صدور", + "import": "وارد کردن داشبورد", + "export": "صادر کردن داشبورد", + "export-failed-error": "{{error}} :صدور داشبورد ممکن نيست", + "create-new-dashboard": "ايجاد داشبورد جديد", + "dashboard-file": "پرونده داشبورد", + "invalid-dashboard-file-error": ".وارد کردن داشبورد ممکن نيست: ساختار داده داشبورد نامعتبر است", + "dashboard-import-missing-aliases-title": "پيکربندي نامهاي مستعار استفاده شده توسط داشبوردِ وارده", + "create-new-widget": "ايجاد ويجت جديد", + "import-widget": "وارد کردن ويجت", + "widget-file": "پرونده ويجت", + "invalid-widget-file-error": ".وارد کردن ويجت ممکن نيست: ساختار داده ويجت نامعتبر است", + "widget-import-missing-aliases-title": "پيکربندي نامهاي مستعار استفاده شده توسط ويجتِ وارده", + "open-toolbar": "باز کردن نوار ابزار داشبورد", + "close-toolbar": "بستن نوار ابزار", + "configuration-error": "خطاي پيکربندي", + "alias-resolution-error-title": "خطاي پيکربندي نامهاي مستعار داشبورد", + "invalid-aliases-config": ".لطفا جهت حل اين موضوع با مسئول مربوط به خود تماس بگيريد
.يافتن دستگاهي منطبق بر فبلتر بعضي نامهاي مستعار ممکن نيست", + "select-devices": "انتخاب دستگاه ها", + "assignedToCustomer": "تخصيص يافته به مشتري", + "assignedToCustomers": "تخصيص يافته به مشتريان", + "public": "عمومي", + "public-link": "پيوند عمومي", + "copy-public-link": "رونوشت از پيوند عمومي", + "public-link-copied-message": "پيوند عمومي داشبورد در حافظه موقت رونوشت شد", + "manage-states": "مديريت وضعيت هاي داشبورد", + "states": "وضعيت هاي داشبورد", + "search-states": "جستجوي وضعيت هاي داشبورد", + "selected-states": "انتخاب شدند { count, plural, 1 {1 وضعيت داشبورد} other {# وضعيت داشبورد} }", + "edit-state": "ويرايش وضعيت داشبورد", + "delete-state": "حذف وضعيت داشبورد", + "add-state": "افزودن وضعيت داشبورد", + "state": "وضعيت داشبورد", + "state-name": "نام", + "state-name-required": ".نام وضعيت داشبورد مورد نياز است", + "state-id": "وضعيت ID", + "state-id-required": ".وضعيت داشبورد مورد نياز است ID", + "state-id-exists": ".مشابه موجود است ID در حال حاضر وضعيت داشبوردي با", + "is-root-state": "وضعيت پايه", + "delete-state-title": "حذف وضعيت داشبورد", + "delete-state-text": "مطمئنيد؟ '{{stateName}}' از حذف وضعيت داشبورد با نام", + "show-details": "نمايش جزئيات", + "hide-details": "پنهان کردن جزئيات", + "select-state": "انتخاب وضعيت هدف", + "state-controller": "کنترل کننده وضعيت" + }, + "datakey": { + "settings": "تنظيمات", + "advanced": "پيشرفته", + "label": "برچسب", + "color": "رنگ", + "units": "کارکتر خاص براي نمايش بعد از مقدار تعين شده", + "decimals": "تعداد ارقام بعد از مميّز شناور", + "data-generation-func": "تابع توليد داده", + "use-data-post-processing-func": "استفاده از تابع پس پردازش داده", + "configuration": "پيکربندي کليد داده", + "timeseries": "سري هاي زماني", + "attributes": "ويژگي ها", + "alarm": "حوزه هاي هشدار", + "timeseries-required": ".سري هاي زماني موجودي مورد نياز است", + "timeseries-or-attributes-required": ".سري هاي زماني / ويژگي هاي موجودي مورد نياز است", + "maximum-timeseries-or-attributes": "{ count, plural, 1 {.1 سري زماني / ويژگي مجاز است} other {# سري زماني / ويژگي مجازند} } بيشترين", + "alarm-fields-required": ".حوزه هاي هشدار مورد نياز است", + "function-types": "نوع توابع", + "function-types-required": ".نوع تابع مورد نياز است", + "maximum-function-types": "{ count, plural, 1 {.1 نوع تابع مجاز است} other {# نوع تابع مجازند} } بيشترين", + "time-description": "برچسب زماني مقدار فعلي؛", + "value-description": "مقدار فعلي؛", + "prev-value-description": "نتيجه ي فراخوانيِ تابع قبلي؛", + "time-prev-description": "برچسب زماني مقدار قبلي؛", + "prev-orig-value-description": "ممقدار اصلي قبلي" + }, + "datasource": { + "type": "نوع منبع داده", + "name": "نام", + "add-datasource-prompt": "لطفا منبع داده را اضافه کنيد" + }, + "details": { + "edit-mode": "حالت ويرايش", + "toggle-edit-mode": "حالت ويرايش را تغيير دهيد" + }, + "device": { + "device": "دستگاه", + "device-required": ".دستگاه مورد نياز است", + "devices": "دستگاه ها", + "management": "مديريت دستگاه", + "view-devices": "نمايش دستگاه ها", + "device-alias": "نام مستعار دستگاه", + "aliases": "نامهاي مستعار دستگاه", + "no-alias-matching": ".يافت نشد'{{alias}}'", + "no-aliases-found": ".هيچ نام مستعاري يافت نشد", + "no-key-matching": ".يافت نشد'{{key}}'", + "no-keys-found": ".هيچ کليدي يافت نشد", + "create-new-alias": "!ايجاد يک نام مستعار جديد", + "create-new-key": "!ايجاد يک کليد جديد", + "duplicate-alias-error": ".نام مستعار در داشبورد بايد يکتا باشد
'{{alias}}'نام مستعار مشابه يافت شد", + "configure-alias": "نام مستعار '{{alias}}' پيکربندي", + "no-devices-matching": "مطابقت داشته باشد وجود ندارد '{{entity}}' هيچ دستگاهي که با ", + "alias": "نام مستعار", + "alias-required": ".نام مستعار مورد نياز است", + "remove-alias": "حذف نام مستعار دستگاه", + "add-alias": "افزودن نام مستعار دستگاه", + "name-starts-with": "اسم دستگاه شروع مي شود با", + "device-list": "ليست دستگاه ها", + "use-device-name-filter": "از فيلتر استفاده کنيد", + "device-list-empty": ".هيچ دستگاهي انتخاب نشده است", + "device-name-filter-required": ".فيلتر نام دستگاه مورد نياز است", + "device-name-filter-no-device-matched": ".شروع شود يافت نشد '{{device}}' هيچ دستگاهي که با", + "add": "افزودن دستگاه", + "assign-to-customer": "تخصيص به مشتري", + "assign-device-to-customer": "تخصيص دستگاه (ها) به مشتري", + "assign-device-to-customer-text": "لطفا دستگاه ها را انتخاب کنيد تا به مشتري تخصيص يابد", + "make-public": "عمومي سازي دستگاه", + "make-private": "شخصي سازي دستگاه", + "no-devices-text": "هيچ دستگاهي يافت نشد", + "assign-to-customer-text": "لطفا مشتري را انتخاب کنيد تا دستگاه(ها) تخصيص يابد", + "device-details": "جزئيات دستگاه", + "add-device-text": "افزودن دستگاه جديد", + "credentials": "اعتبارنامه ها", + "manage-credentials": "مديريت اعتبارنامه ها", + "delete": "حذف دستگاه", + "assign-devices": "تخصيص دستگاه ها", + "assign-devices-text": "به مشتري { count, plural, 1 {1 دستگاه} other {# دستگاه} } تخصيص", + "delete-devices": "حذف دستگاه ها", + "unassign-from-customer": "لغو تخصيص از مشتري", + "unassign-devices": "لغو تخصيص دستگاه ها", + "unassign-devices-action-title": "از مشتري { count, plural, 1 {1 دستگاه} other {# دستگاه} } لغو تخصيص", + "assign-new-device": "تخصيص دستگاه جديد", + "make-public-device-title": "مطمئنيد؟ '{{deviceName}}' از عمومي سازي دستگاه", + "make-public-device-text": ".پس از تأييد، دستگاه و تمامي داده هايش عمومي و قابل دسترسي براي ديگران مي شود", + "make-private-device-title": "مطمئنيد؟ '{{deviceName}}' از شخصي سازي دستگاه", + "make-private-device-text": ".پس از تأييد، دستگاه و تمامي داده هايش شخصي و خارج از دسترس ديگران مي شوند", + "view-credentials": "نمايش اعتبارنامه ها", + "delete-device-title": "مطمئنيد؟ '{{deviceName}}' از حذف", + "delete-device-text": ".مراقب باشيد، پس از تأييد، دستگاه و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند", + "delete-devices-title": "مطمئنيد؟ { count, plural, 1 {1 دستگاه} other {# دستگاه} } از حذف", + "delete-devices-action-title": "{ count, plural, 1 {1 دستگاه} other {# دستگاه} } حذف", + "delete-devices-text": ".مراقب باشيد، پس از تأييد، تمام دستگاه هاي انتخاب شده، حذف، و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند", + "unassign-device-title": "مطمئنيد؟ '{{deviceName}}' از لغو تخصيص", + "unassign-device-text": ".پس از تأييد، دستگاه، لغو تخصيص و خارج از دسترس مشتري مي شود", + "unassign-device": "لغو تخصيص دستگاه", + "unassign-devices-title": "مطمئنيد؟ { count, plural, 1 {1 دستگاه} other {# دستگاه} } از لغو تخصيص", + "unassign-devices-text": ".پس از تأييد، تمام دستگاه هاي انتخاب شده، لغو تخصيص و خارج از دسترس مشتري مي شوند", + "device-credentials": "اعتبارنامه هاي دستگاه", + "credentials-type": "نوع اعتبارنامه ها", + "access-token": "شناسه دسترسي", + "access-token-required": ".شناسه دسترسي مورد نياز است", + "access-token-invalid": ".طول شناسه دسترسي بايد از 1 تا 20 حرف باشد", + "rsa-key": "RSA کليد عمومي", + "rsa-key-required": ".مورد نياز است RSA کليد عمومي", + "secret": "محرمانه", + "secret-required": ".شناسه محرمانه مورد نياز است", + "device-type": "نوع دستگاه", + "device-type-required": ".نوع دستگاه مورد نياز است", + "select-device-type": "انتخاب نوع دستگاه", + "enter-device-type": "وارد کردن نوع دستگاه", + "any-device": "هر دستگاهي", + "no-device-types-matching": ".يافت نشد '{{entitySubtype}}' هيچ نوع دستگاهي منطبق بر", + "device-type-list-empty": ".هيچ نوع دستگاهي انتخاب نشد", + "device-types": "انواع دستگاه", + "name": "نام", + "name-required": ".نام مورد نياز است", + "description": "توصيف", + "events": "رويدادها", + "details": "جزئيات", + "copyId": "دستگاه ID رونوشت از", + "copyAccessToken": "رونوشت از شناسه دسترسي", + "idCopiedMessage": ".دستگاه در حافظه موقت رونوشت شد ID", + "accessTokenCopiedMessage": ".شناسه دسترسي دستگاه در حافظه موقت رونوشت شد", + "assignedToCustomer": "تخصيص يافته به مشتري", + "unable-delete-device-alias-title": "حذف نام مستعار دستگاه ممکن نيست", + "unable-delete-device-alias-text": "
{{widgetsList}} :را تا زمان استفاده توسط ويجت(هاي) زير نمي توان حذف کرد ، '{{deviceAlias}}' ،نام مستعار دستگاه", + "is-gateway": "درگاه است", + "public": "عمومي", + "device-public": "دستگاه عمومي است", + "select-device": "انتخاب دستگاه" + }, + "dialog": { + "close": "بستن گفتگو" + }, + "error": { + "unable-to-connect": ".اتصال به سِروِر ممکن نيست! لطفا اتصال اينترنت خود را بررسي کنيد", + "unhandled-error-code": "{{errorCode}} :کد خطاي رسيدگي نشده", + "unknown-error": "خطاي ناشناخته" + }, + "entity": { + "entity": "موجودي", + "entities": "موجودي ها", + "aliases": "نامهاي مستعار موجودي", + "entity-alias": "نام مستعار موجودي", + "unable-delete-entity-alias-title": "حذف نام مستعار موجودي ممکن نيست", + "unable-delete-entity-alias-text": "
{{widgetsList}} :را تا زمان استفاده توسط ويجت(هاي) زير نمي توان حذف کرد ، '{{entityAlias}}' ،نام مستعار موجودي", + "duplicate-alias-error": ".نامهاي مستعار موجودي بايد در داشبورد، منحصر بفرد باشند
.يافت شد '{{alias}}' نام مستعار تکراري", + "missing-entity-filter-error": ".مفقود است '{{alias}}' فيلتر براي نام مستعار", + "configure-alias": "'{{alias}}' پيکربندي نام مستعار", + "alias": "نام مستعار", + "alias-required": ".نام مستعار موجودي مورد نياز است", + "remove-alias": "حذف نام مستعار موجودي", + "add-alias": "افزودن نام مستعار موجودي", + "entity-list": "ليست موجودي", + "entity-type": "نوع موجودي", + "entity-types": "انواع موجودي", + "entity-type-list": "ليست نوع موجودي", + "any-entity": "هر موجودي", + "enter-entity-type": "وارد کردن نوع موجودي", + "no-entities-matching": ".يافت نشد '{{entity}}' هيچ موجودي منطبق بر", + "no-entity-types-matching": ".يافت نشد '{{entityType}}' هيچ نوع موجودي منطبق بر", + "name-starts-with": "نام شروع مي شود با", + "use-entity-name-filter": "استفاده از فيلتر", + "entity-list-empty": ".هيچ موجودي اي انتخاب نشده است", + "entity-type-list-empty": ".هيچ نوع موجودي انتخاب نشده است", + "entity-name-filter-required": ".فيلتر نام موجودي مورد نياز است", + "entity-name-filter-no-entity-matched": ".شروع شود يافت نشد '{{entity}}' هيچ موجودي که با", + "all-subtypes": "همه", + "select-entities": "انتخاب موجودي ها", + "no-aliases-found": ".هيچ نام مستعاري يافت نشد", + "no-alias-matching": ".يافت نشد '{{alias}}'", + "create-new-alias": "!ايجاد يک نام مستعار جديد", + "key": "کليد", + "key-name": "نام کليد", + "no-keys-found": ".هيچ کليدي يافت نشد", + "no-key-matching": "'.يافت نشد {{key}}'", + "create-new-key": "!ايجاد يک کليد جديد", + "type": "نوع", + "type-required": ".نوع موجودي مورد نياز است", + "type-device": "دستگاه", + "type-devices": "دستگاه ها", + "list-of-devices": "{ count, plural, 1 {يک دستگاه} other {ليست # دستگاه} }", + "device-name-starts-with": "شروع مي شود '{{prefix}}' دستگاه هايي که نامشان با", + "type-asset": "دارايي", + "type-assets": "دارايي ها", + "list-of-assets": "{ count, plural, 1 {يک دارايي} other {ليست # دارايي} }", + "asset-name-starts-with": "شروع مي شود '{{prefix}}' دارايي هايي که نامشان با", + "type-entity-view": "نمايش موجودي", + "type-entity-views": "نمايش هاي موجودي", + "list-of-entity-views": "{ count, plural, 1 {يک نمايش موجودي} other {ليست # نمايش موجودي} }", + "entity-view-name-starts-with": "شروع مي شود '{{prefix}}' نمايش هاي موجودي که نامشان با", + "type-rule": "قاعده", + "type-rules": "قواعد", + "list-of-rules": "{ count, plural, 1 {يک قاعده} other {ليست # قاعده} }", + "rule-name-starts-with": "شروع مي شود '{{prefix}}' قواعدي که نامشان با", + "type-plugin": "ابزار جانبي", + "type-plugins": "ابزارهاي جانبي", + "list-of-plugins": "{ count, plural, 1 {يک ابزار جانبي} other {ليست # ابزار جانبي} }", + "plugin-name-starts-with": "شروع مي شود '{{prefix}}' ابزارهاي جانبي که نامشان با", + "type-tenant": "کاربر", + "type-tenants": "کاربران", + "list-of-tenants": "{ count, plural, 1 {يک کاربر} other {ليست # کاربر} }", + "tenant-name-starts-with": "شروع مي شود '{{prefix}}' کاربرهايي که نامشان با", + "type-customer": "مشتري", + "type-customers": "مشتريان", + "list-of-customers": "{ count, plural, 1 {يک مشتري} other {ليست # مشتري} }", + "customer-name-starts-with": "شروع مي شود '{{prefix}}' مشترياني که نامشان با", + "type-user": "کاربر", + "type-users": "کاربران", + "list-of-users": "{ count, plural, 1 {يک کاربر} other {ليست # کاربر} }", + "user-name-starts-with": "شروع مي شود '{{prefix}}' کاربرهايي که نامشان با", + "type-dashboard": "داشبورد", + "type-dashboards": "داشبوردها", + "list-of-dashboards": "{ count, plural, 1 {يک داشبورد} other {ليست # داشبورد} }", + "dashboard-name-starts-with": "شروع مي شود '{{prefix}}' داشبوردهايي که نامشان با", + "type-alarm": "هشدار", + "type-alarms": "هشدارها", + "list-of-alarms": "{ count, plural, 1 {يک هشدار} other {ليست # هشدار} }", + "alarm-name-starts-with": "شروع مي شود '{{prefix}}' هشدارهايي که نامشان با", + "type-rulechain": "زنجيره قواعد", + "type-rulechains": "زنجيره هاي قواعد", + "list-of-rulechains": "{ count, plural, 1 {يک زنجيره قواعد} other {ليست # زنجيره قواعد} }", + "rulechain-name-starts-with": "شروع مي شود '{{prefix}}' زنجيره هاي قواعدي که نامشان با", + "type-rulenode": "گره قواعد", + "type-rulenodes": "گره هاي قواعد", + "list-of-rulenodes": "{ count, plural, 1 {يک گره قواعد} other {ليست # گره قواعد} }", + "rulenode-name-starts-with": "شروع مي شود '{{prefix}}' گره هاي قواعدي که نامشان با", + "type-current-customer": "مشتري فعلي", + "search": "جستجوي موجودي ها", + "selected-entities": "انتخاب شدند { count, plural, 1 {1 موجودي} other {# موجودي} }", + "entity-name": "نام موجودي", + "details": "جزئيات موجودي", + "no-entities-prompt": "هيچ موجودي اي يافت نشد", + "no-data": "هيچ داده اي براي نمايش نيست", + "columns-to-display": "ستون ها براي نمايش" + }, + "entity-view": { + "entity-view": "نمايش موجودي", + "entity-view-required": ".نمايش موجودي مورد نياز است", + "entity-views": "نمايش هاي موجودي", + "management": "مديريت نمايش موجودي", + "view-entity-views": "نمايش نمايش هاي موجودي", + "entity-view-alias": "نام مستعار نمايش موجودي", + "aliases": "نامهاي مستعار نمايش موجودي", + "no-alias-matching": ".يافت نشد '{{alias}}'", + "no-aliases-found": ".هيچ نام مستعاري يافت نشد", + "no-key-matching": ".يافت نشد '{{key}}'", + "no-keys-found": ".هيچ کليدي يافت نشد", + "create-new-alias": "!ايجاد نام مستعار جديد", + "create-new-key": "!ايجاد کليد جديد", + "duplicate-alias-error": ".نامهاي مستعار نمايش موجودي بايد در داشبورد، منحصر بفرد باشند
.يافت شد '{{alias}}' نام مستعار تکراري", + "configure-alias": "'{{alias}}' پيکربندي نام مستعار", + "no-entity-views-matching": ".يافت نشد '{{entity}}' هيچ موجودي منطبق بر", + "alias": "نام مستعار", + "alias-required": ".نام مستعار نمايش موجودي مورد نياز است", + "remove-alias": "حذف نام مستعار نمايش موجودي", + "add-alias": "افزودن نام مستعار نمايش موجودي", + "name-starts-with": "نام نمايش موجودي شروع مي شود با", + "entity-view-list": "ليست نمايش موجودي", + "use-entity-view-name-filter": "استفاده از فيلتر", + "entity-view-list-empty": ".هيچ نمايش موجودي انتخاب نشد", + "entity-view-name-filter-required": ".فيلتر نام نمايش موجودي مورد نياز است", + "entity-view-name-filter-no-entity-view-matched": ".شروع شود يافت نشد '{{entityView}}' هيچ نمايش موجودي که با", + "add": "افزودن نمايش موجودي", + "assign-to-customer": "تخصيص به مشتري", + "assign-entity-view-to-customer": "تخصيص نمايش(هاي) موجودي به مشتري", + "assign-entity-view-to-customer-text": ".لطفا نمايش هاي موجودي را انتخاب کنيد تا به مشتري تخصيص يابند", + "no-entity-views-text": "هيچ نمايش موجودي يافت نشد", + "assign-to-customer-text": ".لطفا مشتري را انتخاب کنيد تا نمايش(هاي) موجودي تخصيص يابد", + "entity-view-details": "جزئيات نمايش موجودي", + "add-entity-view-text": "افزودن نمايش موجودي جديد", + "delete": "حذف نمايش موجودي", + "assign-entity-views": "تخصيص نمايش هاي موجودي", + "assign-entity-views-text": "به مشتري { count, plural, 1 {1 نمايش موجودي} other {# نمايش موجودي} } تخصيص", + "delete-entity-views": "حذف نمايش هاي موجودي", + "unassign-from-customer": "لغو تخصيص از مشتري", + "unassign-entity-views": "لغو تخصيص نمايش هاي موجودي", + "unassign-entity-views-action-title": "از مشتري { count, plural, 1 {1 نمايش موجودي} other {# نمايش موجودي} } لغو تخصيص", + "assign-new-entity-view": "تخصيص نمايش موجودي جديد", + "delete-entity-view-title": "مطمئنيد؟ '{{entityViewName}}' از حذف نمايش موجودي", + "delete-entity-view-text": ".مراقب باشيد، پس از تأييد، نمايش موجودي و تمامي داده هاي مربوطه، غير قابل بازيابي مي شوند", + "delete-entity-views-title": "مطمئنيد؟ { count, plural, 1 {1 نمايش موجودي} other {# نمايش موجودي} } از حذف نمايش موجودي", + "delete-entity-views-action-title": "{ count, plural, 1 {1 نمايش موجودي} other {# نمايش موجودي} } حذف", + "delete-entity-views-text": ".مراقب باشيد، پس از تأييد، تمام نمايش هاي موجوديِ انتخاب شده حذف، و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند", + "unassign-entity-view-title": "مطمئنيد؟ '{{entityViewName}}' از لغو تخصيص نمايش موجودي", + "unassign-entity-view-text": ".پس از تأييد، نمايش موجودي، لغو تخصيص و خارج از دسترس مشتري مي شود", + "unassign-entity-view": "لغو تخصيص نمايش موجودي", + "unassign-entity-views-title": "مطمئنيد؟ { count, plural, 1 {1 نمايش موجودي} other {# نمايش موجودي} } از لغو تخصيص", + "unassign-entity-views-text": ".پس از تأييد، تمام نمايش هاي موجوديِ انتخاب شده، لغو تخصيص و خارج از دسترس مشتري مي شوند", + "entity-view-type": "نوع نمايش موجودي", + "entity-view-type-required": ".نوع نمايش موجودي مورد نياز است", + "select-entity-view-type": "انتخاب نوع نمايش موجودي", + "enter-entity-view-type": "وارد کردن نوع نمايش موجودي", + "any-entity-view": "هر نمايش موجودي", + "no-entity-view-types-matching": ".يافت نشد '{{entitySubtype}}' هيچ نوع نمايش موجودي منطبق بر", + "entity-view-type-list-empty": ".هيچ نوع نمايش موجودي انتخاب نشد", + "entity-view-types": "انواع نمايش موجودي", + "name": "نام", + "name-required": ".نام مورد نياز است", + "description": "توصيف", + "events": "رويدادها", + "details": "جزئيات", + "copyId": "نمايش موجودي ID رونوشت از", + "assignedToCustomer": "تخصيص يافته به مشتري", + "unable-entity-view-device-alias-title": ".حذف نام مستعار نمايش موجودي ممکن نيست", + "unable-entity-view-device-alias-text": "
{{widgetsList}} :را تا زمان استفاده توسط ويجت(هاي) زير نمي توان حذف کرد ، '{{entityViewAlias}}' ،نام مستعار دستگاه", + "select-entity-view": "انتخاب نمايش موجودي", + "make-public": "عمومي سازي نمايش موجودي", + "start-date": "تاريخ شروع", + "start-ts": "زمان شروع", + "end-date": "تاريخ پايان", + "end-ts": "زمان پايان", + "date-limits": "محدوده تاريخ", + "client-attributes": "ويژگي هاي مشتري", + "shared-attributes": "ويژگي هاي مشترک", + "server-attributes": "ويژگي هاي سِروِر", + "timeseries": "سري هاي زماني", + "client-attributes-placeholder": "ويژگي هاي مشتري", + "shared-attributes-placeholder": "ويژگي هاي مشترک", + "server-attributes-placeholder": "ويژگي هاي سِروِر", + "timeseries-placeholder": "سري هاي زماني", + "target-entity": "موجودي هدف", + "attributes-propagation": "انتشار ويژگي ها", + "attributes-propagation-hint": "هر بار که شما نمايش موجودي را بروز رساني يا ذخيره مي کنيد، نمايش موجودي بصور خودکار ويژگي هاي تعيين شده را از موجودي هدف کپي مي کند و به دلايل عملکردي، ويژگي هاي موجودي هدف، با هر بار تغيير ويژگي، در نمايش موجودي انتشار نمي يابند. مي توانيد با پيکربندي گره قواعد در زنجيره قواعد خود و پيوند دهي \"Post attributes\" و \"Attributes Updated\" آن به گره قواعد جديد، انتشار خودکار \"copy to view\" را ممکن سازيد ." , + "timeseries-data": "داده ي سري هاي زماني", + "timeseries-data-hint": "کليدهاي داده ي سري هاي زمانيِ موجوديِ هدف را پيکربندي کنيد تا در دسترسِ نمايش موجودي باشند. اين سري هاي زماني، فقط خواندني است" + }, + "event": { + "event-type": "نوع رويداد", + "type-error": "خطا", + "type-lc-event": "رويداد چرخه عمر", + "type-stats": "آمار", + "type-debug-rule-node": "اشکال زدايي", + "type-debug-rule-chain": "اشکال زدايي", + "no-events-prompt": "هيچ رويدادي يافت نشد", + "error": "خطا", + "alarm": "هشدار", + "event-time": "زمان رويداد", + "server": "سِروِر", + "body": "بدنه", + "method": "روش", + "type": "نوع", + "entity": "موجودي", + "message-id": "پيام ID", + "message-type": "نوع پيام", + "data-type": "نوع داده", + "relation-type": "نوع ارتباط", + "metadata": "فرا داده", + "data": "داده", + "event": "رويداد", + "status": "وضعيت", + "success": "موفقيت", + "failed": "عدم موفقيت", + "messages-processed": "پيام پردازش شد", + "errors-occurred": "خطاها رخ دادند" + }, + "extension": { + "extensions": "دنباله ها", + "selected-extensions": "انتخاب شدند { count, plural, 1 {1 افزونه} other {افزونه ها #} }", + "type": "نوع", + "key": "کليد", + "value": "مقدار", + "id": "ID", + "extension-id": " افزونه ID", + "extension-type": "نوع افزونه", + "transformer-json": "JSON *", + "unique-id-required": ".افزونه فعلي موجود است ID در حال حاضر", + "delete": "حذف دنباله", + "add": "افزودن دنباله", + "edit": "ويرايش دنباله", + "delete-extension-title": "مطمئنيد؟ '{{extensionId}}' از حذف افزونه", + "delete-extension-text": ".مراقب باشيد، پس از تأييد، افزونه و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند", + "delete-extensions-title": "مطمئنيد؟ { count, plural, 1 {1 افزونه} other {# افزونه} } از حذف", + "delete-extensions-text": ".مراقب باشيد، پس از تأييد، تمام افزونه هاي انتخاب شده حذف مي گردند", + "converters": "مبدّل ها", + "converter-id": "مبدّل ID", + "configuration": "پيکربندي", + "converter-configurations": "پيکربندي هاي مبدّل", + "token": "نشانه امنيت", + "add-converter": "افزودن مبدّل", + "add-config": "افزودن پيکربندي مبدّل", + "device-name-expression": "عبارت نام دستگاه", + "device-type-expression": "عبارت نوع دستگاه", + "custom": "متداول", + "to-double": "دو برابر شدن", + "transformer": "مبدّل", + "json-required": ".مبدّل مورد نياز است JSON", + "json-parse": ".مبدّل ممکن نيست JSON تجزيه", + "attributes": "ويژگي ها", + "add-attribute": "افزودن ويژگي", + "add-map": "افزودن جزء نگاشت", + "timeseries": "سري هاي زماني", + "add-timeseries": "افزودن سري هاي زماني", + "field-required": "دامنه مورد نياز است", + "brokers": "واسطه ها", + "add-broker": "افزودن واسطه", + "host": "ميزبان", + "port": "درگاه", + "port-range": ".درگاه بايد در بازه اي بين 1 تا 65535 باشد", + "ssl": "Ssl", + "credentials": "اعتبارنامه ها", + "username": "نام کاربري", + "password": "رمز عبور", + "retry-interval": "بازخواني فاصله در ميلي ثانيه", + "anonymous": "بي نام", + "basic": "پايه", + "pem": "PEM", + "ca-cert": "CA پرونده گواهينامه *", + "private-key": "پرونده کليد شخصي *", + "cert": "پرونده گواهينامه *", + "no-file": ".هيچ پرونده اي انتخاب نشد", + "drop-file": ".جهت بارگذاري يک پرونده، آن را با موس کِشيده و رها کنيد، و يا روي آن کليک نماييد", + "mapping": "نگاشت", + "topic-filter": "فيلتر عنوان", + "converter-type": "نوع مبدّل", + "converter-json": "JSON", + "json-name-expression": "نام دستگاه JSON عبارت", + "topic-name-expression": "عبارت عنوان نام دستگاه", + "json-type-expression": "نوع دستگاه JSON عبارت", + "topic-type-expression": "عبارت عنوان نوع دستگاه", + "attribute-key-expression": "عبارت کليد ويژگي", + "attr-json-key-expression": "کليد ويژگي JSON عبارت", + "attr-topic-key-expression": "عبارت عنوان کليد ويژگي", + "request-id-expression": "ID درخواست عبارت", + "request-id-json-expression": "ID JSON درخواست عبارت", + "request-id-topic-expression": "ID درخواست عبارت عنوان", + "response-topic-expression": "عبارت عنوان پاسخ", + "value-expression": "عبارت مقدار", + "topic": "عنوان", + "timeout": "وقفه در ميلي ثانيه", + "converter-json-required": ".مبدّل مورد نياز است JSON", + "converter-json-parse": ".مبدّل ممکن نيست JSON تجزيه", + "filter-expression": "عبارت فيلتر", + "connect-requests": "درخواست هاي اتصال", + "add-connect-request": "افزودن درخواست اتصال", + "disconnect-requests": "درخواست هاي قطع اتصال", + "add-disconnect-request": "افزودن درخواست قطع اتصال", + "attribute-requests": "درخواست هاي ويژگي", + "add-attribute-request": "افزودن درخواست ويژگي", + "attribute-updates": "به روز رساني هاي ويژگي ", + "add-attribute-update": "افزودن به روز رساني ويژگي ", + "server-side-rpc": "سَمت سِروِر RPC", + "add-server-side-rpc-request": "سَمت سِروِر RPC افزودن درخواست", + "device-name-filter": "فيلتر نام دستگاه", + "attribute-filter": "فيلتر ويژگي ", + "method-filter": "فيلتر روش", + "request-topic-expression": "عبارت عنوان درخواست", + "response-timeout": "وقفه پاسخ در ميلي ثانيه", + "topic-expression": "بيان موضوع", + "client-scope": "حوزه مشتري", + "add-device": "افزودن دستگاه", + "opc-server": "سِروِرها", + "opc-add-server": "افزودن سِروِر", + "opc-add-server-prompt": "لطفا سِروِر را اضافه کنيد", + "opc-application-name": "نام برنامه کاربردي", + "opc-application-uri": "برنامه کاربردي URI", + "opc-scan-period-in-seconds": "دوره پويش در ثانيه", + "opc-security": "امنيت", + "opc-identity": "هويت", + "opc-keystore": "کي استور", + "opc-type": "نوع", + "opc-keystore-type": "نوع", + "opc-keystore-location": "* موقعيت مکاني", + "opc-keystore-password": "رمز عبور", + "opc-keystore-alias": "نام مستعار", + "opc-keystore-key-password": "کليد رمز عبور", + "opc-device-node-pattern": "الگوي گره دستگاه", + "opc-device-name-pattern": "الگوي نام دستگاه", + "modbus-server": "سِروِرها/جايگزين آماده به کار", + "modbus-add-server": "افزودن سِروِر/ جايگزين آماده به کار ", + "modbus-add-server-prompt": "لطفا سِروِرها/جايگزين آماده به کار را اضافه کنيد", + "modbus-transport": "انتقال", + "modbus-port-name": "نام در گاه سريال", + "modbus-encoding": "رمز گذاري", + "modbus-parity": "توازن", + "modbus-baudrate": "نرخ علامت در ثانيه", + "modbus-databits": "بيت هاي داده", + "modbus-stopbits": "بيت هاي توقف", + "modbus-databits-range": ".بيت هاي داده بايد در بازه اي بين 7 تا 8 باشند", + "modbus-stopbits-range": ".بيت هاي توقف بايد در بازه اي بين 1 تا 2 باشند", + "modbus-unit-id": "واحد ID", + "modbus-unit-id-range": ".واحد بايد در بازه اي بين 1 تا 247 باشد ID", + "modbus-device-name": "نام دستگاه", + "modbus-poll-period": "(ms) دوره نمونه برداري", + "modbus-attributes-poll-period": "(ms) دوره نمونه برداري ويژگي ها", + "modbus-timeseries-poll-period": "(ms) دوره نمونه برداري سري هاي زماني", + "modbus-poll-period-range": ".دوره نمونه برداري بايد مقداري مثبت باشد", + "modbus-tag": "برچسب", + "modbus-function": "تابع", + "modbus-register-address": "ثبت نام نشاني", + "modbus-register-address-range": ".نشاني ثبت بايد در بازه اي بين 0 تا 65535 باشد", + "modbus-register-bit-index": "شاخص بيت", + "modbus-register-bit-index-range": ".شاخص بيت بايد در بازه اي بين 0 تا 15 باشد", + "modbus-register-count": "شمارش ثبت", + "modbus-register-count-range": ".شمارش ثبت بايد مقداري مثبت باشد", + "modbus-byte-order": "ترتيب بايت", + + "sync": { + "status": "وضعيت", + "sync": "همگام", + "not-sync": "غير همگام", + "last-sync-time": "آخرين زمان همگام سازي", + "not-available": "خارج از دسترس" + }, + + "export-extensions-configuration": "صدور پيکربندي افزونه ها", + "import-extensions-configuration": "وارد کردن پيکربندي افزونه ها", + "import-extensions": "وارد کردن افزونه ها", + "import-extension": "وارد کردن افزونه", + "export-extension": "صدور افزونه", + "file": "پرونده افزونه ها", + "invalid-file-error": "پرونده افزونه نامعتبر است" + }, + "fullscreen": { + "expand": "بسط به حالت تمام صفحه", + "exit": "خروج از حالت تمام صفحه", + "toggle": "تغيير حالت تمام صفحه", + "fullscreen": "حالت تمام صفحه" + }, + "function": { + "function": "تابع" + }, + "grid": { + "delete-item-title": "از حذف اين مورد مطمئنيد؟", + "delete-item-text": ".مراقب باشيد، پس از تأييد، اين مورد و تمامي داده هاي مربوطه غيرقابل بازيابي مي شوند", + "delete-items-title": "مطمئنيد؟ { count, plural, 1 {1 مورد} other {# مورد} } از حذف", + "delete-items-action-title": "{ count, plural, 1 {1 مورد} other {# مورد} } حذف", + "delete-items-text": ".مراقب باشيد، پس از تأييد، تمام مواردِ انتخاب شده حذف، و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند", + "add-item-text": "افزودن مورد جديد", + "no-items-text": "هيچ موردي يافت نشد", + "item-details": "جزئيات مورد", + "delete-item": "حذف مورد", + "delete-items": "حذف موارد", + "scroll-to-top": "پيمايش به بالا" + }, + "help": { + "goto-help-page": "رفتن به صفحه کمک" + }, + "home": { + "home": "خانه", + "profile": "پرونده شخصي", + "logout": "خروج", + "menu": "فهرست انتخاب", + "avatar": "آواتار", + "open-user-menu": "بازکردن فهرست انتخاب کاربر" + }, + "import": { + "no-file": "هيچ پرونده اي انتخاب نشد", + "drop-file": ".آن را با موس کِشيده و رها کنيد، و يا روي آن کليک نماييد ،JSON جهت بارگذاري يک پرونده" + }, + "item": { + "selected": "انتخاب شده" + }, + "js-func": { + "no-return-error": "!تابع بايد مقدار را برگرداند", + "return-type-mismatch": "!را برگرداند '{{type}}' تابع بايد مقدار نوع", + "tidy": "مرتب" + }, + "key-val": { + "key": "کليد", + "value": "مقدار", + "remove-entry": "حذف ورودي", + "add-entry": "افزودن ورودي", + "no-data": "هيچ ورودي وجود ندارد" + }, + "layout": { + "layout": "طرح بندي", + "manage": "مديريت طرح بندي ها", + "settings": "تنظيمات طرح بندي", + "color": "رنگ", + "main": "اصلي", + "right": "راست", + "select": "انتخاب طرح بندي هدف" + }, + "legend": { + "position": "محل فهرست علائم", + "show-max": "نمايش بيشترين مقدار", + "show-min": "نمايش کمترين مقدار", + "show-avg": "نمايش مقدار ميانگين", + "show-total": "نمايش مقدار مجموع", + "settings": "تنظيمات فهرست علائم", + "min": "کمترين", + "max": "بيشترين", + "avg": "ميانگين", + "total": "مجموع" + }, + "login": { + "login": "ورود", + "request-password-reset": "درخواست بازنشاني رمز عبور", + "reset-password": "بازنشاني رمز عبور", + "create-password": "ايجاد رمز عبور", + "passwords-mismatch-error": "!رمزهاي عبور وارد شده بايد مشابه باشند", + "password-again": "رمز عبور دوباره", + "sign-in": "لطفا وارد شويد", + "username": "(نام کاربري (پست الکترونيک", + "remember-me": "مرا به خاطر داشته باش", + "forgot-password": "رمز عبور را فراموش کرده ايد؟", + "password-reset": "بازنشاني رمز عبور", + "new-password": "رمز عبور جديد", + "new-password-again": "رمز عبور جديد دوباره", + "password-link-sent-message": "!پيوند بازنشاني رمز عبور با موفقيت ارسال شد", + "email": "پست الکترونيک" + }, + "position": { + "top": "بالا", + "bottom": "پايين", + "left": "چپ", + "right": "راست" + }, + "profile": { + "profile": "پرونده شخصي", + "change-password": "تغيير رمز عبور", + "current-password": "رمز عبور فعلي" + }, + "relation": { + "relations": "ارتباطات", + "direction": "جهت", + "search-direction": { + "FROM": "از", + "TO": "به" + }, + "direction-type": { + "FROM": "از", + "TO": "به" + }, + "from-relations": "ارتباطات خارج از محدوده", + "to-relations": "ارتباطات داخل محدوده", + "selected-relations": "انتخاب شدند { count, plural, 1 {1 ارتباط} other {ارتباط #} }", + "type": "نوع", + "to-entity-type": "به نوع موجودي", + "to-entity-name": "به نام موجودي", + "from-entity-type": "از نوع موجودي", + "from-entity-name": "از نام موجودي", + "to-entity": "به موجودي", + "from-entity": "از موجودي", + "delete": "حذف ارتباط", + "relation-type": "نوع ارتباط", + "relation-type-required": ".نوع ارتباط مورد نياز است", + "any-relation-type": "هر نوع", + "add": "افزودن ارتباط", + "edit": "ويرايش ارتباط", + "delete-to-relation-title": "مطمئنيد؟ '{{entityName}}' از حذف ارتباط با موجودي", + "delete-to-relation-text": ".غيرمرتبط با موجودي فعلي مي شود '{{entityName}}' مراقب باشيد، پس از تأييد، موجودي", + "delete-to-relations-title": "مطمئنيد؟ { count, plural, 1 {1 ارتباط} other {# ارتباط} } از حذف", + "delete-to-relations-text": ".مراقب باشيد، پس از تأييد، تمام روابطِ انتخاب شده حذف، و موجودي هاي مربوطه، غيرمرتبط با موجودي فعلي مي شوند", + "delete-from-relation-title": "مطمئنيد؟ '{{entityName}}' از حذف ارتباط از موجودي", + "delete-from-relation-text": ".مي شود '{{entityName}}' مراقب باشيد، پس از تأييد، موجودي فعلي، غيرمرتبط از جانب موجودي", + "delete-from-relations-title": "مطمئنيد؟ { count, plural, 1 {1 ارتباط} other {# ارتباط} } از حذف", + "delete-from-relations-text": ".مراقب باشيد، پس از تأييد، تمام روابطِ انتخاب شده حذف، و موجودي فعلي، غيرمرتبط از جانب موجودي هاي مربوطه مي شود", + "remove-relation-filter": "حذف فيلتر ارتباط", + "add-relation-filter": "افزودن فيلتر ارتباط", + "any-relation": "هر ارتباط", + "relation-filters": "فيلترهاي ارتباط", + "additional-info": "(JSON) اطلاعات تکميلي", + "invalid-additional-info": "اطلاعات تکميلي ممکن نيست JSON تجزيه" + }, + "rulechain": { + "rulechain": "زنجيره قواعد", + "rulechains": "زنجيره هاي قواعد", + "root": "پايه", + "delete": "حذف زنجيره قواعد", + "name": "نام", + "name-required": ".نام مورد نياز است", + "description": "توصيف", + "add": "افزودن زنجيره قواعد", + "set-root": "ريشه زنجيره قواعد را ايجاد کنيد", + "set-root-rulechain-title": "به عنوان ريشه مطمئنيد؟ '{{ruleChainName}}' از قرار دادن زنجيره قواعد", + "set-root-rulechain-text": ".پس از تأييد، زنجيره قواعد، به عنوان ريشه تعيين شده و به تمام پيامهاي انتقالي رسيدگي مي کند", + "delete-rulechain-title": "مطمئنيد؟ '{{ruleChainName}}' از حذف زنجيره قواعد", + "delete-rulechain-text": ".مراقب باشيد، پس از تأييد، زنجيره قواعد و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند", + "delete-rulechains-title": "مطمئنيد؟ { count, plural, 1 {1 زنجيره قواعد} other {# زنجيره قواعد} } از حذف", + "delete-rulechains-action-title": "{ count, plural, 1 {1 زنجيره قواعد} other {# زنجيره قواعد} } حذف", + "delete-rulechains-text": ".مراقب باشيد، پس از تأييد، تمام زنجيره هاي قواعدِ انتخاب شده حذف، و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند", + "add-rulechain-text": "افزودن زنجيره قواعد جديد", + "no-rulechains-text": "هيچ زنجيره قواعدي يافت نشد", + "rulechain-details": "جزئيات زنجيره قواعد", + "details": "جزئيات", + "events": "رويدادها", + "system": "سيستم", + "import": "وارد کردن زنجيره قواعد", + "export": "صدور زنجيره قواعد", + "export-failed-error": "{{error}} :صدور زنجيره قواعد ممکن نيست", + "create-new-rulechain": "ايجاد زنجيره قواعد جديد", + "rulechain-file": "پرونده زنجيره قواعد", + "invalid-rulechain-file-error": ".وارد کردن زنجيره قواعد ممکن نيست: ساختار داده زنجيره قواعد نامعتبر است", + "copyId": "زنجيره قواعد ID رونوشت", + "idCopiedMessage": "زنجيره قواعد در حافظه موقت رونوشت شد ID", + "select-rulechain": "انتخاب زنجيره قواعد", + "no-rulechains-matching": ".يافت نشد '{{entity}}' هيچ زنجيره قواعدي منطبق بر", + "rulechain-required": "زنجيره قواعد مورد نياز است", + "management": "مديريت قواعد", + "debug-mode": "حالت اشکال زدايي" + }, + "rulenode": { + "details": "جزئيات", + "events": "رويدادها", + "search": "جستجوي گره ها", + "open-node-library": "باز کردن کتابخانه گره ها", + "add": "افزودن گره قواعد", + "name": "نام", + "name-required": ".نام مورد نياز است", + "type": "نوع", + "description": "توصيف", + "delete": "حذف گره قواعد", + "select-all-objects": "انتخاب تمام گره ها و اتصالات", + "deselect-all-objects": "لغو انتخاب تمام گره ها و اتصالات", + "delete-selected-objects": "حذف گره ها و اتصالاتِ انتخاب شده", + "delete-selected": "حذفِ انتخاب شده", + "select-all": "انتخاب همه", + "copy-selected": "رونوشتِ انتخاب شده", + "deselect-all": "لغو انتخاب همه", + "rulenode-details": "جزئيات گره قواعد", + "debug-mode": "حالت اشکال زدايي", + "configuration": "پيکربندي", + "link": "پيوند", + "link-details": "جزئيات پيوند گره قواعد", + "add-link": "افزودن پيوند", + "link-label": "برچسب پيوند", + "link-label-required": ".برچسب پيوند مورد نياز است", + "custom-link-label": "برچسب پيوند متداول", + "custom-link-label-required": ".برچسب پيوند متداول مورد نياز است", + "link-labels": "برچسب هاي پيوند", + "link-labels-required": ".برچسب هاي پيوند مورد نيازند", + "no-link-labels-found": "هيچ برچسب پيوندي يافت نشد", + "no-link-label-matching": ".پيدا نشد'{{label}}'", + "create-new-link-label": "!برچسب پيوند ايجاد کنيد", + "type-filter": "فيلتر", + "type-filter-details": "پيام هاي ورودي را با شرايط پيکربندي فيلتر کنيد", + "type-enrichment": "افزودن", + "type-enrichment-details": "افزودن اطلاعات تکميلي به فرا داده ي پيام", + "type-transformation": "تبديل", + "type-transformation-details": "تغيير بازده و فرا داده ي پيام", + "type-action": "اقدام", + "type-action-details": "انجام اقدام ويژه", + "type-external": "خارجي", + "type-external-details": "ارتباط متقابل با سيستم خارجي", + "type-rule-chain": "زنجيره قواعد", + "type-rule-chain-details": "ارسال پيامهاي وارده به زنجيره قواعدي مشخص", + "type-input": "ورودي", + "type-input-details": "ورودي منطقي زنجيره قواعد، پيامهاي ورودي را به گره قواعد مرتبط بعدي ارسال مي کند", + "type-unknown": "ناشناخته", + "type-unknown-details": "گره قواعدِ حل نشده", + "directive-is-not-loaded": ".در دسترس نيست '{{directiveName}}' دستورالعمل پيکربنديِ مشخص شده", + "ui-resources-load-error": ".پيکربندي UI عدم موفقيت در بارگذاري منابع", + "invalid-target-rulechain": "!حلّ زنجيره قواعد هدف ممکن نيست", + "test-script-function": "آزمايش تابع اسکريپت", + "message": "پيام", + "message-type": "نوع پيام", + "select-message-type": "انتخاب نوع پيام", + "message-type-required": "نوع پيام مورد نياز است", + "metadata": "فوق داده", + "metadata-required": ".ورودي هاي فرا داده نمي تواند خالي باشد", + "output": "خروجي", + "test": "آزمايش", + "help": "کمک" + }, + "tenant": { + "tenant": "کاربر", + "tenants": "کاربران", + "management": "مديريت کاربران", + "add": "افزودن کاربر", + "admins": "سرپرستان", + "manage-tenant-admins": "مديريت مديران کاربر", + "delete": "حذف کاربر", + "add-tenant-text": "افزودن کاربر جديد", + "no-tenants-text": "هيچ کاربري يافت نشد", + "tenant-details": "جزئيات کاربر", + "delete-tenant-title": "مطمئنيد؟ '{{tenantTitle}}' از حذف کاربر", + "delete-tenant-text": ".مراقب باشيد، پس از تأييد، کاربر و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند", + "delete-tenants-title": "مطمئنيد؟ { count, plural, 1 {1 کاربر} other {# کاربر} } از حذف", + "delete-tenants-action-title": "{ count, plural, 1 {1 کاربر} other {# کاربر} } حذف", + "delete-tenants-text": ".مراقب باشيد، پس از تأييد، تمام کاربران حذف، و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند", + "title": "عنوان", + "title-required": ".عنوان مورد نياز است", + "description": "توصيف", + "details": "جزئيات", + "events": "رويدادها", + "copyId": "متصرّف ID رونوشت", + "idCopiedMessage": "کاربر در حافظه موقت رونوشت شد ID", + "select-tenant": "انتخاب کاربر", + "no-tenants-matching": ".يافت نشد '{{entity}}' هيچ کاربري منطبق بر", + "tenant-required": "کاربر مورد نياز است" + }, + "timeinterval": { + "seconds-interval": "{ seconds, plural, 1 {1 ثانيه} other {ثانيه #} }", + "minutes-interval": "{ minutes, plural, 1 {1 دقيقه} other {دقيقه #} }", + "hours-interval": "{ hours, plural, 1 {1 ساعت} other {ساعت #} }", + "days-interval": "{ days, plural, 1 {1 روز} other {روز #} }", + "days": "روزها", + "hours": "ساعات", + "minutes": "دقايق", + "seconds": "ثانيه ها", + "advanced": "پيشرفته" + }, + "timewindow": { + "days": "{ days, plural, 1 { روز } other {روز #} }", + "hours": "{ hours, plural, 0 { 1ساعت } 1 { ساعت } other {# ساعت } }", + "minutes": "{ minutes, plural, 0 { 1دقيقه } 1 { دقيقه } other {دقيقه # } }", + "seconds": "{ seconds, plural, 0 { 1ثانيه } 1 { ثانيه } other {ثانيه # } }", + "realtime": "بي درنگ", + "history": "تاريخچه", + "last-prefix": "آخرين", + "period": "{{ endTime }} تا {{ startTime }} از", + "edit": "ويرايش پنجره زماني", + "date-range": "بازه داده", + "last": "آخرين", + "time-period": "دوره زماني" + }, + "user": { + "user": "کاربر", + "users": "کاربرها", + "customer-users": "کاربرهاي مشتري", + "tenant-admins": "مديران کاربر", + "sys-admin": "مدير سيستم", + "tenant-admin": "کاربر مدير", + "customer": "مشتري", + "anonymous": "بي نام", + "add": "افزودن کاربر", + "delete": "حذف کاربر", + "add-user-text": "افزودن کاربر جديد", + "no-users-text": "هيچ کاربري يافت نشد", + "user-details": "جزئيات کاربر", + "delete-user-title": "مطمئنيد؟ '{{userEmail}}' از حذف کاربر", + "delete-user-text": ".مراقب باشيد، پس از تأييد، کاربر و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند", + "delete-users-title": "مطمئنيد؟ { count, plural, 1 {1 کاربر} other {# کاربر} } از حذف", + "delete-users-action-title": "{ count, plural, 1 {1 کاربر} other {کاربر #} } حذف", + "delete-users-text": ".مراقب باشيد، پس از تأييد، تمام کاربرهاي انتخاب شده حذف، و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند", + "activation-email-sent-message": "!پست الکترونيک فعال سازي با موفقيت ارسال شد", + "resend-activation": "ارسال مجدد فعال سازي", + "email": "پست الکترونيک", + "email-required": ".پست الکترونيک مورد نياز است", + "invalid-email-format": ".قالب نامعتبر پست الکترونيک", + "first-name": "نام", + "last-name": "نام خانوادگي", + "description": "توصيف", + "default-dashboard": "داشبورد پيش فرض", + "always-fullscreen": "همواره در حالت تمام صفحه", + "select-user": "انتخاب کاربر", + "no-users-matching": ".يافت نشد '{{entity}}' هيچ کاربري منطبق بر", + "user-required": "کاربر مورد نياز است", + "activation-method": "روش فعال سازي", + "display-activation-link": "نمايش پيوند فعال سازي", + "send-activation-mail": "ارسال پست الکترونيک فعال سازي", + "activation-link": "پيوند فعال سازي کاربر", + "activation-link-text": ":
استفاده کنيد جهت فعال سازي کاربر، از پيوند فعال سازي زير", + "copy-activation-link": "رونوشت پيوند فعال سازي", + "activation-link-copied-message": "پيوند فعال سازي کاربر در حافظه موقت رونوشت شد", + "details": "جزئيات", + "login-as-tenant-admin": "ورود به عنوان کاربر مدير", + "login-as-customer-user": "ورود به عنوان کاربر مشتري" + }, + "value": { + "type": "نوع مقدار", + "string": "رشته", + "string-value": "مقدار رشته", + "integer": "عدد صحيح", + "integer-value": "مقدار عدد صحيح", + "invalid-integer-value": "عدد صحيح نامعتبر", + "double": "دو برابر", + "double-value": "مقدار دو برابر", + "boolean": "بولين", + "boolean-value": "مقدار بولين", + "false": "نادرست", + "true": "صحيح", + "long": "بلند" + }, + "widget": { + "widget-library": "کتابخانه ويجت ها", + "widget-bundle": "بسته ويجت", + "select-widgets-bundle": "انتخاب بسته ويجت", + "management": "مديريت ويجت", + "editor": "ويرايشگر ويجت", + "widget-type-not-found": ".نوع ويجت حذف شد \n احتمالا مرتبط است
.مشکل بارگذاري پيکربندي ويجت", + "widget-type-load-error": ":ويجت، به علت خطاهاي زير، بارگذاري نشد", + "remove": "حذف ويجت", + "edit": "ويرايش ويجت", + "remove-widget-title": "مطمئنيد؟ '{{widgetTitle}}' از حذف ويجت", + "remove-widget-text": ".پس از تأييد، ويجت و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند", + "timeseries": "سري هاي زماني", + "search-data": "جستجوي داده", + "no-data-found": "هيچ داده اي يافت نشد", + "latest-values": "آخرين مقادير", + "rpc": "ويجت کنترل", + "alarm": "ويجت هشدار", + "static": "ويجت ايستا", + "select-widget-type": "انتخاب نوع ويجت", + "missing-widget-title-error": "!عنوان ويجت بايد مشخص شود", + "widget-saved": "ويجت ذخيره شد", + "unable-to-save-widget-error": "!ذخيره سازي ويجت ممکن نيست! ويجت خطاهايي دارد", + "save": "ذخيره سازي ويجت", + "saveAs": "ذخيره سازي ويجت به عنوان", + "save-widget-type-as": "ذخيره سازي نوع ويجت به عنوان", + "save-widget-type-as-text": "لطفا عنوان ويجت جديد را وارد کنيد و/يا بسته ويجت هدف را انتخاب نماييد", + "toggle-fullscreen": "حالت تمام صفحه را تغيير دهيد", + "run": "اجراي ويجت", + "title": "عنوان ويجت", + "title-required": ".عنوان ويجت مورد نياز است", + "type": "نوع ويجت", + "resources": "منابع", + "resource-url": "JavaScript/CSS URL", + "remove-resource": "حذف منبع", + "add-resource": "افزودن منبع", + "html": "HTML", + "tidy": "مرتب", + "css": "CSS", + "settings-schema": "طرح تنظيمات", + "datakey-settings-schema": "طرح تنظيمات کليد داده", + "javascript": "Javascript", + "remove-widget-type-title": "مطمئنيد؟ '{{widgetName}}' از حذف ويجت نوع", + "remove-widget-type-text": ".پس از تأييد، نوع ويجت و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند", + "remove-widget-type": "حذف نوع ويجت", + "add-widget-type": "افزودن نوع ويجت جديد", + "widget-type-load-failed-error": "!عدم موفقيت در بارگذاري نوع ويجت", + "widget-template-load-failed-error": "!عدم موفقيت در بارگذاري قالب ويجت", + "add": "افزودن ويجت", + "undo": "برگرداندن تغييرات ويجت", + "export": "صدور ويجت" + }, + "widget-action": { + "header-button": "دکمه هدر ويجت", + "open-dashboard-state": "هدايت به وضعيت داشبورد جديد", + "update-dashboard-state": "به روز رساني وضعيت داشبورد فعلي", + "open-dashboard": "هدايت به داشبورد ديگر", + "custom": "اقدام متداول", + "target-dashboard-state": "وضعيت داشبورد هدف", + "target-dashboard-state-required": "وضعيت داشبورد هدف مورد نياز است", + "set-entity-from-widget": "تنظيم موجودي از ويجت", + "target-dashboard": "داشبورد هدف", + "open-right-layout": "(طرح داشبورد سمت راست را باز کنيد (نماي تلفن همراه" + }, + "widgets-bundle": { + "current": "بسته فعلي", + "widgets-bundles": "بسته هاي ويجت", + "add": "افزودن بسته ويجت", + "delete": "حذف بسته ويجت", + "title": "عنوان", + "title-required": ".عنوان مورد نياز است", + "add-widgets-bundle-text": "افزودن بسته ويجت جديد", + "no-widgets-bundles-text": "هيچ بسته ويجتي يافت نشد", + "empty": "بسته ويجت خالي است", + "details": "جزئيات", + "widgets-bundle-details": "جزئيات بسته ويجت", + "delete-widgets-bundle-title": "مطمئنيد؟ '{{widgetsBundleTitle}}' از حذف بسته ويجت", + "delete-widgets-bundle-text": ".مراقب باشيد، پس از تأييد، بسته ويجت و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند", + "delete-widgets-bundles-title": "مطمئنيد؟ { count, plural, 1 {1 بسته ويجت} other {# بسته ويجت} } از حذف", + "delete-widgets-bundles-action-title": "{ count, plural, 1 {1 بسته ويجت} other {# بسته ويجت} } حذف", + "delete-widgets-bundles-text": ".مراقب باشيد، پس از تأييد، تمام بسته هاي ويجتِ انتخاب شده حذف، و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند", + "no-widgets-bundles-matching": "يافت نشد '{{widgetsBundle}}' هيچ بسته ويجتي منطبق بر", + "widgets-bundle-required": ".بسته ويجت مورد نياز است", + "system": "سيستم", + "import": "وارد کردن بسته ويجت", + "export": "صدور بسته ويجت", + "export-failed-error": "{{error}} :صدور بسته ويجت ممکن نيست", + "create-new-widgets-bundle": "ايجاد بسته ويجت جديد", + "widgets-bundle-file": "پرونده بسته ويجت", + "invalid-widgets-bundle-file-error": ".وارد کردن بسته ويجت ممکن نيست: ساختار داده بسته ويجت نامعتبر است" + }, + "widget-config": { + "data": "داده", + "settings": "تنظيمات", + "advanced": "پيشرفته", + "title": "عنوان", + "general-settings": "تنظيمات عمومي", + "display-title": "نمايش عنوان", + "drop-shadow": "سايه افتادن", + "enable-fullscreen": "فعال سازي حالت تمام صفحه", + "background-color": "رنگ پس زمينه", + "text-color": "رنگ متن", + "padding": "حاشيه داخلي", + "margin": "حاشيه", + "widget-style": "سبک ويجت", + "title-style": "سبک عنوان", + "mobile-mode-settings": "تنظيمات حالت تلفن همراه", + "order": "ترتيب", + "height": "ارتفاع", + "units": "کارکتر خاص براي نمايش بعد از مقدار تعين شده", + "decimals": "تعداد ارقام بعد از مميّز شناور", + "timewindow": "پنجره زمان", + "use-dashboard-timewindow": "استفاده از پنجره زمان داشبورد", + "display-legend": "نمايش فهرست علائم", + "datasources": "منابع داده", + "maximum-datasources": "{ count, plural, 1 {.1 منبع داده مجاز است} other {# منبع داده مجازند.} } بيشترين", + "datasource-type": "نوع", + "datasource-parameters": "پارامترها", + "remove-datasource": "حذف منبع داده", + "add-datasource": "افزودن منبع داده", + "target-device": "دستگاه هدف", + "alarm-source": "منشأ هشدار", + "actions": "اقدامات", + "action": "اقدام", + "add-action": "افزودن اقدام", + "search-actions": "جستجوي اقدامات", + "action-source": "منشأ اقدام", + "action-source-required": ".منشأ اقدام مورد نياز است", + "action-name": "نام", + "action-name-required": ".نام اقدام مورد نياز است", + "action-name-not-unique": ".در حيطه يک منشأ اقدام، نام اقدام بايد منحصر بفرد باشد
.در حال حاضر اقدامي ديگر با نام مشابه موجود است", + "action-icon": "شمايل", + "action-type": "نوع", + "action-type-required": ".نوع اقدام مورد نياز است", + "edit-action": "ويرايش اقدام", + "delete-action": "حذف اقدام", + "delete-action-title": "حذف اقدام ويجت", + "delete-action-text": "مطمئنيد؟ '{{actionName}}' از حذف اقدام ويجت با نام" + }, + "widget-type": { + "import": "وارد کردن نوع ويجت", + "export": "صدور نوع ويجت", + "export-failed-error": "{{error}} :صدور نوع ويجت ممکن نيست", + "create-new-widget-type": "ايجاد نوع جديد ويجت", + "widget-type-file": "پرونده نوع ويجت", + "invalid-widget-type-file-error": ".وارد کردن نوع ويجت ممکن نيست: ساختار داده نوع ويجت نامعتبر است" + }, + "icon": { + "icon": "آيکون", + "select-icon": "انتخاب آيکون", + "material-icons": "آيکونهاي اجسام", + "show-all": "نمايش تمام آيکونها" + }, + "custom": { + "widget-action": { + "action-cell-button": "دکمه سلول عملياتي", + "row-click": "در رديف کليک کنيد", + "marker-click": "روي نشانگر کليک کنيد", + "tooltip-tag-action": "اقدام برچسب راهنماي ابزار" + } + }, + "language": { + "language": "زبان", + "locales": { + "de_DE": "آلمانی", + "fr_FR": "فرانسوي", + "zh_CN": "چيني", + "en_US": "انگليسي", + "it_IT": "ايتاليايي", + "ko_KR": "کره اي", + "ru_RU": "روسي", + "es_ES": "اسپانيولي", + "ja_JA": "ژاپني", + "tr_TR": "ترکي", + "fa_IR": "فارسي" + } + } +} diff --git a/ui/src/app/locale/locale.constant-fr_FR.json b/ui/src/app/locale/locale.constant-fr_FR.json index 5b4edd95c4..651b695779 100644 --- a/ui/src/app/locale/locale.constant-fr_FR.json +++ b/ui/src/app/locale/locale.constant-fr_FR.json @@ -1011,7 +1011,9 @@ "ko_KR": "Coréen", "ru_RU": "Russe", "zh_CN": "Chinois", - "tr_TR": "Turc" + "ja_JA": "Japonaise", + "tr_TR": "Turc", + "fa_IR": "Persane" } }, "layout": { diff --git a/ui/src/app/locale/locale.constant-it_IT.json b/ui/src/app/locale/locale.constant-it_IT.json index 4c73548df6..1d3a84fe25 100644 --- a/ui/src/app/locale/locale.constant-it_IT.json +++ b/ui/src/app/locale/locale.constant-it_IT.json @@ -1441,7 +1441,8 @@ "ru_RU": "Russo", "es_ES": "Spagnolo", "ja_JA": "Giapponese", - "tr_TR": "Turco" + "tr_TR": "Turco", + "fa_IR": "Persiana" } } } \ No newline at end of file diff --git a/ui/src/app/locale/locale.constant-ja_JA.json b/ui/src/app/locale/locale.constant-ja_JA.json index fea4ab335d..ce4d849216 100644 --- a/ui/src/app/locale/locale.constant-ja_JA.json +++ b/ui/src/app/locale/locale.constant-ja_JA.json @@ -1450,6 +1450,7 @@ "language": "言語", "locales": { "de_DE": "ドイツ語", + "fr_FR": "フランス語", "en_US": "英語", "ko_KR": "韓国語", "it_IT": "イタリアの", @@ -1457,7 +1458,8 @@ "ru_RU": "ロシア", "es_ES": "スペイン語", "ja_JA": "日本語", - "tr_TR": "トルコ語" + "tr_TR": "トルコ語", + "fa_IR": "ペルシャ語" } } } \ No newline at end of file diff --git a/ui/src/app/locale/locale.constant-ko_KR.json b/ui/src/app/locale/locale.constant-ko_KR.json index 4ddd9b5afa..57aef0e525 100644 --- a/ui/src/app/locale/locale.constant-ko_KR.json +++ b/ui/src/app/locale/locale.constant-ko_KR.json @@ -1334,7 +1334,8 @@ "es_ES": "스페인어", "it_IT": "이탈리아 사람", "ja_JA": "일본어", - "tr_TR": "터키어" + "tr_TR": "터키어", + "fa_IR": "페르시아 인" } } } \ No newline at end of file diff --git a/ui/src/app/locale/locale.constant-ru_RU.json b/ui/src/app/locale/locale.constant-ru_RU.json index c087649789..d31d8666bc 100644 --- a/ui/src/app/locale/locale.constant-ru_RU.json +++ b/ui/src/app/locale/locale.constant-ru_RU.json @@ -1573,7 +1573,8 @@ "ru_RU": "Русский", "tr_TR": "Турецкий", "fr_FR": "Французский", - "ja_JA": "Японский" + "ja_JA": "Японский", + "fa_IR": "Персидский" } } } \ No newline at end of file diff --git a/ui/src/app/locale/locale.constant-tr_TR.json b/ui/src/app/locale/locale.constant-tr_TR.json index 3f2e37e0f7..b96eaaf3ce 100644 --- a/ui/src/app/locale/locale.constant-tr_TR.json +++ b/ui/src/app/locale/locale.constant-tr_TR.json @@ -1540,7 +1540,8 @@ "ru_RU": "Rusça", "es_ES": "İspanyol", "ja_JA": "Japonca", - "tr_TR": "Türkçe" + "tr_TR": "Türkçe", + "fa_IR": "Farsça" } } } \ No newline at end of file diff --git a/ui/src/app/locale/locale.constant-zh_CN.json b/ui/src/app/locale/locale.constant-zh_CN.json index bacddc4763..141c613065 100644 --- a/ui/src/app/locale/locale.constant-zh_CN.json +++ b/ui/src/app/locale/locale.constant-zh_CN.json @@ -1444,7 +1444,8 @@ "es_ES": "西班牙语", "it_IT": "意大利", "ja_JA": "日本", - "tr_TR": "土耳其" + "tr_TR": "土耳其", + "fa_IR": "波斯语" } } } \ No newline at end of file diff --git a/ui/webpack.config.dev.js b/ui/webpack.config.dev.js index bea9275315..dec2c77423 100644 --- a/ui/webpack.config.dev.js +++ b/ui/webpack.config.dev.js @@ -25,7 +25,6 @@ const path = require('path'); const dirTree = require('directory-tree'); const jsonminify = require("jsonminify"); -const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); const HappyPack = require('happypack'); const PUBLIC_RESOURCE_PATH = '/'; @@ -99,10 +98,6 @@ module.exports = { PUBLIC_PATH: JSON.stringify(PUBLIC_RESOURCE_PATH), SUPPORTED_LANGS: JSON.stringify(langs) }), - new UglifyJsPlugin({ - cache: true, - parallel: true - }), new HappyPack({ threadPool: happyThreadPool, id: 'cached-babel',