diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAdminControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAdminControllerTest.java index 43b8f6caf1..61fa5bd526 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseAdminControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseAdminControllerTest.java @@ -92,19 +92,7 @@ public abstract class BaseAdminControllerTest extends AbstractControllerTest { .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString("is prohibited"))); } - - @Test - public void testSaveAdminSettingsWithNewJsonStructure() throws Exception { - loginSysAdmin(); - AdminSettings adminSettings = doGet("/api/admin/settings/mail", AdminSettings.class); - JsonNode json = adminSettings.getJsonValue(); - ((ObjectNode) json).put("newKey", "my new value"); - adminSettings.setJsonValue(json); - doPost("/api/admin/settings", adminSettings) - .andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Provided json structure is different"))); - } - + @Test public void testSendTestMail() throws Exception { loginSysAdmin(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java index a016d02d73..6c7495cc19 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java @@ -73,9 +73,6 @@ public class AdminSettingsServiceImpl implements AdminSettingsService { if (!existentAdminSettings.getKey().equals(adminSettings.getKey())) { throw new DataValidationException("Changing key of admin settings entry is prohibited!"); } - if (adminSettings.getKey().equals("mail")) { - validateJsonStructure(existentAdminSettings.getJsonValue(), adminSettings.getJsonValue()); - } } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java index 226fa6df8e..0bd667b790 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java @@ -15,9 +15,11 @@ */ package org.thingsboard.server.dao.sql.attributes; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; +import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.dao.model.sql.AttributeKvCompositeKey; import org.thingsboard.server.dao.model.sql.AttributeKvEntity; @@ -34,5 +36,16 @@ public interface AttributeKvRepository extends CrudRepository findAllByEntityTypeAndEntityIdAndAttributeType(@Param("entityType") EntityType entityType, @Param("entityId") String entityId, @Param("attributeType") String attributeType); + + @Transactional + @Modifying + @Query("DELETE FROM AttributeKvEntity a WHERE a.id.entityType = :entityType " + + "AND a.id.entityId = :entityId " + + "AND a.id.attributeType = :attributeType " + + "AND a.id.attributeKey = :attributeKey") + void delete(@Param("entityType") EntityType entityType, + @Param("entityId") String entityId, + @Param("attributeType") String attributeType, + @Param("attributeKey") String attributeKey); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java index ab9964d3e3..c14e2dd7d0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java @@ -138,16 +138,10 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl @Override public ListenableFuture> removeAll(TenantId tenantId, EntityId entityId, String attributeType, List keys) { - List entitiesToDelete = keys - .stream() - .map(key -> { - AttributeKvEntity entityToDelete = new AttributeKvEntity(); - entityToDelete.setId(new AttributeKvCompositeKey(entityId.getEntityType(), fromTimeUUID(entityId.getId()), attributeType, key)); - return entityToDelete; - }).collect(Collectors.toList()); - return service.submit(() -> { - attributeKvRepository.deleteAll(entitiesToDelete); + keys.forEach(key -> + attributeKvRepository.delete(entityId.getEntityType(), UUIDConverter.fromTimeUUID(entityId.getId()), attributeType, key) + ); return null; }); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/AbstractRelationInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/AbstractRelationInsertRepository.java new file mode 100644 index 0000000000..1f43959c2a --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/AbstractRelationInsertRepository.java @@ -0,0 +1,51 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.relation; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.jpa.repository.Modifying; +import org.thingsboard.server.dao.model.sql.RelationEntity; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.Query; + +@Slf4j +public abstract class AbstractRelationInsertRepository implements RelationInsertRepository { + + @PersistenceContext + protected EntityManager entityManager; + + protected Query getQuery(RelationEntity entity, String query) { + Query nativeQuery = entityManager.createNativeQuery(query, RelationEntity.class); + if (entity.getAdditionalInfo() == null) { + nativeQuery.setParameter("additionalInfo", null); + } else { + nativeQuery.setParameter("additionalInfo", entity.getAdditionalInfo().toString()); + } + return nativeQuery + .setParameter("fromId", entity.getFromId()) + .setParameter("fromType", entity.getFromType()) + .setParameter("toId", entity.getToId()) + .setParameter("toType", entity.getToType()) + .setParameter("relationTypeGroup", entity.getRelationTypeGroup()) + .setParameter("relationType", entity.getRelationType()); + } + + @Modifying + protected abstract RelationEntity processSaveOrUpdate(RelationEntity entity); + +} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/HsqlRelationInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/HsqlRelationInsertRepository.java new file mode 100644 index 0000000000..8438999302 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/HsqlRelationInsertRepository.java @@ -0,0 +1,47 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.relation; + +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; +import org.thingsboard.server.dao.model.sql.RelationCompositeKey; +import org.thingsboard.server.dao.model.sql.RelationEntity; +import org.thingsboard.server.dao.util.HsqlDao; +import org.thingsboard.server.dao.util.SqlDao; + +@HsqlDao +@SqlDao +@Repository +@Transactional +public class HsqlRelationInsertRepository extends AbstractRelationInsertRepository implements RelationInsertRepository { + + private static final String INSERT_ON_CONFLICT_DO_UPDATE = "MERGE INTO relation USING (VALUES :fromId, :fromType, :toId, :toType, :relationTypeGroup, :relationType, :additionalInfo) R " + + "(from_id, from_type, to_id, to_type, relation_type_group, relation_type, additional_info) " + + "ON (relation.from_id = R.from_id AND relation.from_type = R.from_type AND relation.relation_type_group = R.relation_type_group AND relation.relation_type = R.relation_type AND relation.to_id = R.to_id AND relation.to_type = R.to_type) " + + "WHEN MATCHED THEN UPDATE SET relation.additional_info = R.additional_info " + + "WHEN NOT MATCHED THEN INSERT (from_id, from_type, to_id, to_type, relation_type_group, relation_type, additional_info) VALUES (R.from_id, R.from_type, R.to_id, R.to_type, R.relation_type_group, R.relation_type, R.additional_info)"; + + @Override + public RelationEntity saveOrUpdate(RelationEntity entity) { + return processSaveOrUpdate(entity); + } + + @Override + protected RelationEntity processSaveOrUpdate(RelationEntity entity) { + getQuery(entity, INSERT_ON_CONFLICT_DO_UPDATE).executeUpdate(); + return entityManager.find(RelationEntity.class, new RelationCompositeKey(entity.toData())); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java index 2d69d0ae06..6eda5d8426 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java @@ -58,6 +58,9 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple @Autowired private RelationRepository relationRepository; + @Autowired + private RelationInsertRepository relationInsertRepository; + @Override public ListenableFuture> findAllByFrom(TenantId tenantId, EntityId from, RelationTypeGroup typeGroup) { return service.submit(() -> DaoUtil.convertDataList( @@ -119,12 +122,12 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple @Override public boolean saveRelation(TenantId tenantId, EntityRelation relation) { - return relationRepository.save(new RelationEntity(relation)) != null; + return relationInsertRepository.saveOrUpdate(new RelationEntity(relation)) != null; } @Override public ListenableFuture saveRelationAsync(TenantId tenantId, EntityRelation relation) { - return service.submit(() -> relationRepository.save(new RelationEntity(relation)) != null); + return service.submit(() -> relationInsertRepository.saveOrUpdate(new RelationEntity(relation)) != null); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/PsqlRelationInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/PsqlRelationInsertRepository.java new file mode 100644 index 0000000000..dbef233811 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/PsqlRelationInsertRepository.java @@ -0,0 +1,43 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.relation; + +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; +import org.thingsboard.server.dao.model.sql.RelationEntity; +import org.thingsboard.server.dao.util.PsqlDao; +import org.thingsboard.server.dao.util.SqlDao; + +@PsqlDao +@SqlDao +@Repository +@Transactional +public class PsqlRelationInsertRepository extends AbstractRelationInsertRepository implements RelationInsertRepository { + + private static final String INSERT_ON_CONFLICT_DO_UPDATE = "INSERT INTO relation (from_id, from_type, to_id, to_type, relation_type_group, relation_type, additional_info)" + + " VALUES (:fromId, :fromType, :toId, :toType, :relationTypeGroup, :relationType, :additionalInfo) " + + "ON CONFLICT (from_id, from_type, relation_type_group, relation_type, to_id, to_type) DO UPDATE SET additional_info = :additionalInfo returning *"; + + @Override + public RelationEntity saveOrUpdate(RelationEntity entity) { + return processSaveOrUpdate(entity); + } + + @Override + protected RelationEntity processSaveOrUpdate(RelationEntity entity) { + return (RelationEntity) getQuery(entity, INSERT_ON_CONFLICT_DO_UPDATE).getSingleResult(); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationInsertRepository.java new file mode 100644 index 0000000000..fe7dfe05be --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationInsertRepository.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.relation; + +import org.thingsboard.server.dao.model.sql.RelationEntity; + +public interface RelationInsertRepository { + + RelationEntity saveOrUpdate(RelationEntity entity); + +} \ No newline at end of file diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAdminSettingsServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAdminSettingsServiceTest.java index 71aaedef2c..9d5f391dc1 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAdminSettingsServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAdminSettingsServiceTest.java @@ -67,13 +67,4 @@ public abstract class BaseAdminSettingsServiceTest extends AbstractServiceTest { adminSettings.setKey("newKey"); adminSettingsService.saveAdminSettings(SYSTEM_TENANT_ID, adminSettings); } - - @Test(expected = DataValidationException.class) - public void testSaveAdminSettingsWithNewJsonStructure() { - AdminSettings adminSettings = adminSettingsService.findAdminSettingsByKey(SYSTEM_TENANT_ID, "mail"); - JsonNode json = adminSettings.getJsonValue(); - ((ObjectNode) json).put("newKey", "my new value"); - adminSettings.setJsonValue(json); - adminSettingsService.saveAdminSettings(SYSTEM_TENANT_ID, adminSettings); - } } diff --git a/ui/src/app/admin/admin.controller.js b/ui/src/app/admin/admin.controller.js index fa1c9920ee..8a2f04c621 100644 --- a/ui/src/app/admin/admin.controller.js +++ b/ui/src/app/admin/admin.controller.js @@ -25,7 +25,7 @@ export default function AdminController(adminService, toast, $scope, $rootScope, return protocol; }); - vm.tlsVersions = ['TLSv1.0', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3']; + vm.tlsVersions = ['TLSv1', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3']; $translate('admin.test-mail-sent').then(function (translation) { vm.testMailSent = translation; diff --git a/ui/src/app/widget/lib/CanvasDigitalGauge.js b/ui/src/app/widget/lib/CanvasDigitalGauge.js index 424fad3901..d450e09d9c 100644 --- a/ui/src/app/widget/lib/CanvasDigitalGauge.js +++ b/ui/src/app/widget/lib/CanvasDigitalGauge.js @@ -104,26 +104,32 @@ export default class CanvasDigitalGauge extends canvasGauges.BaseGauge { } var colorsCount = options.levelColors.length; - var inc = colorsCount > 1 ? (1 / (colorsCount - 1)) : 1; + const inc = colorsCount > 1 ? (1 / (colorsCount - 1)) : 1; + var isColorProperty = angular.isString(options.levelColors[0]); + options.colorsRange = []; if (options.neonGlowBrightness) { options.neonColorsRange = []; } - for (var i = 0; i < options.levelColors.length; i++) { - var percentage = inc * i; - var tColor = tinycolor(options.levelColors[i]); - options.colorsRange[i] = { - pct: percentage, - color: tColor.toRgb(), - rgbString: tColor.toRgbString() - }; - if (options.neonGlowBrightness) { - tColor = tinycolor(options.levelColors[i]).brighten(options.neonGlowBrightness); - options.neonColorsRange[i] = { + + for (let i = 0; i < options.levelColors.length; i++) { + const levelColor = options.levelColors[i]; + if (levelColor !== null) { + let percentage = isColorProperty ? inc * i : CanvasDigitalGauge.normalizeValue(levelColor.value, options.minValue, options.maxValue); + let tColor = tinycolor(isColorProperty ? levelColor : levelColor.color); + options.colorsRange.push({ pct: percentage, color: tColor.toRgb(), rgbString: tColor.toRgbString() - }; + }); + if (options.neonGlowBrightness) { + tColor = tinycolor(isColorProperty ? levelColor : levelColor.color).brighten(options.neonGlowBrightness); + options.neonColorsRange.push({ + pct: percentage, + color: tColor.toRgb(), + rgbString: tColor.toRgbString() + }); + } } } @@ -137,6 +143,17 @@ export default class CanvasDigitalGauge extends canvasGauges.BaseGauge { return canvasGauges.BaseGauge.configure(options); } + static normalizeValue (value, min, max) { + let normalValue = (value - min) / (max - min); + if (normalValue <= 0) { + return 0; + } + if (normalValue >= 1) { + return 1; + } + return normalValue; + } + destroy() { this.contextValueClone = null; this.elementValueClone = null; diff --git a/ui/src/app/widget/lib/canvas-digital-gauge.js b/ui/src/app/widget/lib/canvas-digital-gauge.js index 71f40d77fa..c0eed09c20 100644 --- a/ui/src/app/widget/lib/canvas-digital-gauge.js +++ b/ui/src/app/widget/lib/canvas-digital-gauge.js @@ -50,10 +50,16 @@ export default class TbCanvasDigitalGauge { this.localSettings.gaugeWidthScale = settings.gaugeWidthScale || 0.75; this.localSettings.gaugeColor = settings.gaugeColor || tinycolor(keyColor).setAlpha(0.2).toRgbString(); - if (!settings.levelColors || settings.levelColors.length <= 0) { - this.localSettings.levelColors = [keyColor]; + this.localSettings.useFixedLevelColor = settings.useFixedLevelColor || false; + if (!settings.useFixedLevelColor) { + if (!settings.levelColors || settings.levelColors.length <= 0) { + this.localSettings.levelColors = [keyColor]; + } else { + this.localSettings.levelColors = settings.levelColors.slice(); + } } else { - this.localSettings.levelColors = settings.levelColors.slice(); + this.localSettings.levelColors = [keyColor]; + this.localSettings.fixedLevelColors = settings.fixedLevelColors || []; } this.localSettings.decimals = angular.isDefined(dataKey.decimals) ? dataKey.decimals : @@ -191,15 +197,137 @@ export default class TbCanvasDigitalGauge { }; this.gauge = new CanvasDigitalGauge(gaugeData).draw(); + this.init(); + } + + init() { + if (this.localSettings.useFixedLevelColor) { + if (this.localSettings.fixedLevelColors && this.localSettings.fixedLevelColors.length > 0) { + this.localSettings.levelColors = this.settingLevelColorsSubscribe(this.localSettings.fixedLevelColors); + this.updateLevelColors(this.localSettings.levelColors); + } + } + } + + settingLevelColorsSubscribe(options) { + let levelColorsDatasource = []; + let predefineLevelColors = []; + + function setLevelColor(levelSetting, color) { + if (levelSetting.valueSource === 'predefinedValue' && isFinite(levelSetting.value)) { + predefineLevelColors.push({ + value: levelSetting.value, + color: color + }) + } else if (levelSetting.entityAlias && levelSetting.attribute) { + let entityAliasId = this.ctx.aliasController.getEntityAliasId(levelSetting.entityAlias); + if (!entityAliasId) { + return; + } + + let datasource = levelColorsDatasource.filter((datasource) => { + return datasource.entityAliasId === entityAliasId; + })[0]; + + let dataKey = { + type: this.ctx.$scope.$injector.get('types').dataKeyType.attribute, + name: levelSetting.attribute, + label: levelSetting.attribute, + settings: [{ + color: color, + index: predefineLevelColors.length + }], + _hash: Math.random() + }; + + if (datasource) { + let findDataKey = datasource.dataKeys.filter((dataKey) => { + return dataKey.name === levelSetting.attribute; + })[0]; + + if (findDataKey) { + findDataKey.settings.push({ + color: color, + index: predefineLevelColors.length + }); + } else { + datasource.dataKeys.push(dataKey) + } + } else { + datasource = { + type: this.ctx.$scope.$injector.get('types').datasourceType.entity, + name: levelSetting.entityAlias, + aliasName: levelSetting.entityAlias, + entityAliasId: entityAliasId, + dataKeys: [dataKey] + }; + levelColorsDatasource.push(datasource); + } + + predefineLevelColors.push(null); + } + } + + for (let i = 0; i < options.length; i++) { + let levelColor = options[i]; + if (levelColor.from) { + setLevelColor.call(this, levelColor.from, levelColor.color); + } + if (levelColor.to) { + setLevelColor.call(this, levelColor.to, levelColor.color); + } + } + this.subscribeLevelColorsAttributes(levelColorsDatasource); + + return predefineLevelColors; + } + + updateLevelColors(levelColors) { + this.gauge.options.levelColors = levelColors; + this.gauge.options = CanvasDigitalGauge.configure(this.gauge.options); + this.gauge.update(); + } + + subscribeLevelColorsAttributes(datasources) { + let TbCanvasDigitalGauge = this; + let levelColorsSourcesSubscriptionOptions = { + datasources: datasources, + useDashboardTimewindow: false, + type: this.ctx.$scope.$injector.get('types').widgetType.latest.value, + callbacks: { + onDataUpdated: (subscription) => { + for (let i = 0; i < subscription.data.length; i++) { + let keyData = subscription.data[i]; + if (keyData && keyData.data && keyData.data[0]) { + let attrValue = keyData.data[0][1]; + if (isFinite(attrValue)) { + for (let i = 0; i < keyData.dataKey.settings.length; i++) { + let setting = keyData.dataKey.settings[i]; + this.localSettings.levelColors[setting.index] = { + value: attrValue, + color: setting.color + }; + } + } + } + } + this.updateLevelColors(this.localSettings.levelColors); + } + } + }; + this.ctx.subscriptionApi.createSubscription(levelColorsSourcesSubscriptionOptions, true).then( + (subscription) => { + TbCanvasDigitalGauge.levelColorSourcesSubscription = subscription; + } + ); } update() { if (this.ctx.data.length > 0) { var cellData = this.ctx.data[0]; if (cellData.data.length > 0) { - var tvPair = cellData.data[cellData.data.length - - 1]; + var tvPair = cellData.data[cellData.data.length - 1]; var timestamp; if (this.localSettings.showTimestamp) { timestamp = tvPair[0]; @@ -325,6 +453,11 @@ export default class TbCanvasDigitalGauge { "type": "string", "default": null }, + "useFixedLevelColor": { + "title": "Use precise value for the color indicator", + "type": "boolean", + "default": false + }, "levelColors": { "title": "Colors of indicator, from lower to upper", "type": "array", @@ -333,6 +466,66 @@ export default class TbCanvasDigitalGauge { "type": "string" } }, + "fixedLevelColors": { + "title": "The colors for the indicator using boundary values", + "type": "array", + "items": { + "title": "levelColor", + "type": "object", + "properties": { + "from": { + "title": "From", + "type": "object", + "properties": { + "valueSource": { + "title": "[From] Value source", + "type": "string", + "default": "predefinedValue" + }, + "entityAlias": { + "title": "[From] Source entity alias", + "type": "string" + }, + "attribute": { + "title": "[From] Source entity attribute", + "type": "string" + }, + "value": { + "title": "[From] Value (if predefined value is selected)", + "type": "number" + } + } + }, + "to": { + "title": "To", + "type": "object", + "properties": { + "valueSource": { + "title": "[To] Value source", + "type": "string", + "default": "predefinedValue" + }, + "entityAlias": { + "title": "[To] Source entity alias", + "type": "string" + }, + "attribute": { + "title": "[To] Source entity attribute", + "type": "string" + }, + "value": { + "title": "[To] Value (if predefined value is selected)", + "type": "number" + } + } + }, + "color": { + "title": "Color", + "type": "string" + } + } + } + }, "animation": { "title": "Enable animation", "type": "boolean", @@ -521,8 +714,10 @@ export default class TbCanvasDigitalGauge { "key": "gaugeColor", "type": "color" }, + "useFixedLevelColor", { "key": "levelColors", + "condition": "model.useFixedLevelColor !== true", "items": [ { "key": "levelColors[]", @@ -530,6 +725,52 @@ export default class TbCanvasDigitalGauge { } ] }, + { + "key": "fixedLevelColors", + "condition": "model.useFixedLevelColor === true", + "items": [ + { + "key": "fixedLevelColors[].from.valueSource", + "type": "rc-select", + "multiple": false, + "items": [ + { + "value": "predefinedValue", + "label": "Predefined value (Default)" + }, + { + "value": "entityAttribute", + "label": "Value taken from entity attribute" + } + ] + }, + "fixedLevelColors[].from.value", + "fixedLevelColors[].from.entityAlias", + "fixedLevelColors[].from.attribute", + { + "key": "fixedLevelColors[].to.valueSource", + "type": "rc-select", + "multiple": false, + "items": [ + { + "value": "predefinedValue", + "label": "Predefined value (Default)" + }, + { + "value": "entityAttribute", + "label": "Value taken from entity attribute" + } + ] + }, + "fixedLevelColors[].to.value", + "fixedLevelColors[].to.entityAlias", + "fixedLevelColors[].to.attribute", + { + "key": "fixedLevelColors[].color", + "type": "color" + } + ] + }, "animation", "animationDuration", {