Showing
9 changed files
with
315 additions
and
139 deletions
@@ -14,24 +14,6 @@ | @@ -14,24 +14,6 @@ | ||
14 | -- limitations under the License. | 14 | -- limitations under the License. |
15 | -- | 15 | -- |
16 | 16 | ||
17 | --- call check_version(); | ||
18 | - | ||
19 | -CREATE OR REPLACE PROCEDURE check_version(INOUT valid_version boolean) LANGUAGE plpgsql AS $BODY$ | ||
20 | -DECLARE | ||
21 | - current_version integer; | ||
22 | -BEGIN | ||
23 | - RAISE NOTICE 'Check the current installed PostgreSQL version...'; | ||
24 | - SELECT current_setting('server_version_num') INTO current_version; | ||
25 | - IF current_version > 110000 THEN | ||
26 | - RAISE NOTICE 'PostgreSQL version is valid!'; | ||
27 | - RAISE NOTICE 'Schema update started...'; | ||
28 | - SELECT true INTO valid_version; | ||
29 | - ELSE | ||
30 | - RAISE NOTICE 'Postgres version should be at least more than 10!'; | ||
31 | - END IF; | ||
32 | -END; | ||
33 | -$BODY$; | ||
34 | - | ||
35 | -- call create_partition_ts_kv_table(); | 17 | -- call create_partition_ts_kv_table(); |
36 | 18 | ||
37 | CREATE OR REPLACE PROCEDURE create_partition_ts_kv_table() LANGUAGE plpgsql AS $$ | 19 | CREATE OR REPLACE PROCEDURE create_partition_ts_kv_table() LANGUAGE plpgsql AS $$ |
@@ -14,25 +14,6 @@ | @@ -14,25 +14,6 @@ | ||
14 | -- limitations under the License. | 14 | -- limitations under the License. |
15 | -- | 15 | -- |
16 | 16 | ||
17 | --- call check_version(); | ||
18 | - | ||
19 | -CREATE OR REPLACE PROCEDURE check_version(INOUT valid_version boolean) LANGUAGE plpgsql AS $BODY$ | ||
20 | - | ||
21 | -DECLARE | ||
22 | - current_version integer; | ||
23 | -BEGIN | ||
24 | - RAISE NOTICE 'Check the current installed PostgreSQL version...'; | ||
25 | - SELECT current_setting('server_version_num') INTO current_version; | ||
26 | - IF current_version > 110000 THEN | ||
27 | - RAISE NOTICE 'PostgreSQL version is valid!'; | ||
28 | - RAISE NOTICE 'Schema update started...'; | ||
29 | - SELECT true INTO valid_version; | ||
30 | - ELSE | ||
31 | - RAISE NOTICE 'Postgres version should be at least more than 10!'; | ||
32 | - END IF; | ||
33 | -END; | ||
34 | -$BODY$; | ||
35 | - | ||
36 | -- call create_new_ts_kv_table(); | 17 | -- call create_new_ts_kv_table(); |
37 | 18 | ||
38 | CREATE OR REPLACE PROCEDURE create_new_ts_kv_table() LANGUAGE plpgsql AS $$ | 19 | CREATE OR REPLACE PROCEDURE create_new_ts_kv_table() LANGUAGE plpgsql AS $$ |
@@ -32,11 +32,8 @@ import java.sql.Statement; | @@ -32,11 +32,8 @@ import java.sql.Statement; | ||
32 | public abstract class AbstractSqlTsDatabaseUpgradeService { | 32 | public abstract class AbstractSqlTsDatabaseUpgradeService { |
33 | 33 | ||
34 | protected static final String CALL_REGEX = "call "; | 34 | protected static final String CALL_REGEX = "call "; |
35 | - protected static final String CHECK_VERSION = "check_version(false)"; | ||
36 | - protected static final String CHECK_VERSION_TO_DELETE = "check_version(INOUT valid_version boolean)"; | ||
37 | protected static final String DROP_TABLE = "DROP TABLE "; | 35 | protected static final String DROP_TABLE = "DROP TABLE "; |
38 | protected static final String DROP_PROCEDURE_IF_EXISTS = "DROP PROCEDURE IF EXISTS "; | 36 | protected static final String DROP_PROCEDURE_IF_EXISTS = "DROP PROCEDURE IF EXISTS "; |
39 | - protected static final String DROP_PROCEDURE_CHECK_VERSION = DROP_PROCEDURE_IF_EXISTS + CHECK_VERSION_TO_DELETE; | ||
40 | 37 | ||
41 | @Value("${spring.datasource.url}") | 38 | @Value("${spring.datasource.url}") |
42 | protected String dbUrl; | 39 | protected String dbUrl; |
@@ -58,13 +55,14 @@ public abstract class AbstractSqlTsDatabaseUpgradeService { | @@ -58,13 +55,14 @@ public abstract class AbstractSqlTsDatabaseUpgradeService { | ||
58 | } | 55 | } |
59 | 56 | ||
60 | protected boolean checkVersion(Connection conn) { | 57 | protected boolean checkVersion(Connection conn) { |
61 | - log.info("Check the current PostgreSQL version..."); | ||
62 | boolean versionValid = false; | 58 | boolean versionValid = false; |
63 | try { | 59 | try { |
64 | Statement statement = conn.createStatement(); | 60 | Statement statement = conn.createStatement(); |
65 | - ResultSet resultSet = statement.executeQuery(CALL_REGEX + CHECK_VERSION); | 61 | + ResultSet resultSet = statement.executeQuery("SELECT current_setting('server_version_num')"); |
66 | resultSet.next(); | 62 | resultSet.next(); |
67 | - versionValid = resultSet.getBoolean(1); | 63 | + if(resultSet.getLong(1) > 110000) { |
64 | + versionValid = true; | ||
65 | + } | ||
68 | statement.close(); | 66 | statement.close(); |
69 | } catch (Exception e) { | 67 | } catch (Exception e) { |
70 | log.info("Failed to check current PostgreSQL version due to: {}", e.getMessage()); | 68 | log.info("Failed to check current PostgreSQL version due to: {}", e.getMessage()); |
@@ -70,16 +70,15 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe | @@ -70,16 +70,15 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe | ||
70 | switch (fromVersion) { | 70 | switch (fromVersion) { |
71 | case "2.4.3": | 71 | case "2.4.3": |
72 | try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { | 72 | try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { |
73 | - log.info("Updating timeseries schema ..."); | ||
74 | - log.info("Load upgrade functions ..."); | ||
75 | - loadSql(conn); | 73 | + log.info("Check the current PostgreSQL version..."); |
76 | boolean versionValid = checkVersion(conn); | 74 | boolean versionValid = checkVersion(conn); |
77 | if (!versionValid) { | 75 | if (!versionValid) { |
78 | - log.info("PostgreSQL version should be at least more than 11!"); | ||
79 | - log.info("Please upgrade your PostgreSQL and restart the script!"); | 76 | + throw new RuntimeException("PostgreSQL version should be at least more than 11, please upgrade your PostgreSQL and restart the script!"); |
80 | } else { | 77 | } else { |
81 | log.info("PostgreSQL version is valid!"); | 78 | log.info("PostgreSQL version is valid!"); |
82 | - log.info("Updating schema ..."); | 79 | + log.info("Load upgrade functions ..."); |
80 | + loadSql(conn); | ||
81 | + log.info("Updating timeseries schema ..."); | ||
83 | executeQuery(conn, CALL_CREATE_PARTITION_TS_KV_TABLE); | 82 | executeQuery(conn, CALL_CREATE_PARTITION_TS_KV_TABLE); |
84 | executeQuery(conn, CALL_CREATE_PARTITIONS); | 83 | executeQuery(conn, CALL_CREATE_PARTITIONS); |
85 | executeQuery(conn, CALL_CREATE_TS_KV_DICTIONARY_TABLE); | 84 | executeQuery(conn, CALL_CREATE_TS_KV_DICTIONARY_TABLE); |
@@ -91,7 +90,6 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe | @@ -91,7 +90,6 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe | ||
91 | executeQuery(conn, DROP_TABLE_TS_KV_OLD); | 90 | executeQuery(conn, DROP_TABLE_TS_KV_OLD); |
92 | executeQuery(conn, DROP_TABLE_TS_KV_LATEST_OLD); | 91 | executeQuery(conn, DROP_TABLE_TS_KV_LATEST_OLD); |
93 | 92 | ||
94 | - executeQuery(conn, DROP_PROCEDURE_CHECK_VERSION); | ||
95 | executeQuery(conn, DROP_PROCEDURE_CREATE_PARTITION_TS_KV_TABLE); | 93 | executeQuery(conn, DROP_PROCEDURE_CREATE_PARTITION_TS_KV_TABLE); |
96 | executeQuery(conn, DROP_PROCEDURE_CREATE_PARTITIONS); | 94 | executeQuery(conn, DROP_PROCEDURE_CREATE_PARTITIONS); |
97 | executeQuery(conn, DROP_PROCEDURE_CREATE_TS_KV_DICTIONARY_TABLE); | 95 | executeQuery(conn, DROP_PROCEDURE_CREATE_TS_KV_DICTIONARY_TABLE); |
@@ -73,16 +73,15 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr | @@ -73,16 +73,15 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr | ||
73 | switch (fromVersion) { | 73 | switch (fromVersion) { |
74 | case "2.4.3": | 74 | case "2.4.3": |
75 | try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { | 75 | try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { |
76 | - log.info("Updating timescale schema ..."); | ||
77 | - log.info("Load upgrade functions ..."); | ||
78 | - loadSql(conn); | 76 | + log.info("Check the current PostgreSQL version..."); |
79 | boolean versionValid = checkVersion(conn); | 77 | boolean versionValid = checkVersion(conn); |
80 | if (!versionValid) { | 78 | if (!versionValid) { |
81 | - log.info("PostgreSQL version should be at least more than 11!"); | ||
82 | - log.info("Please upgrade your PostgreSQL and restart the script!"); | 79 | + throw new RuntimeException("PostgreSQL version should be at least more than 11, please upgrade your PostgreSQL and restart the script!"); |
83 | } else { | 80 | } else { |
84 | log.info("PostgreSQL version is valid!"); | 81 | log.info("PostgreSQL version is valid!"); |
85 | - log.info("Updating schema ..."); | 82 | + log.info("Load upgrade functions ..."); |
83 | + loadSql(conn); | ||
84 | + log.info("Updating timescale schema ..."); | ||
86 | executeQuery(conn, CALL_CREATE_TS_KV_LATEST_TABLE); | 85 | executeQuery(conn, CALL_CREATE_TS_KV_LATEST_TABLE); |
87 | executeQuery(conn, CALL_CREATE_NEW_TENANT_TS_KV_TABLE); | 86 | executeQuery(conn, CALL_CREATE_NEW_TENANT_TS_KV_TABLE); |
88 | 87 | ||
@@ -105,7 +104,7 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr | @@ -105,7 +104,7 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr | ||
105 | executeQuery(conn, "ALTER TABLE ts_kv ADD COLUMN json_v json;"); | 104 | executeQuery(conn, "ALTER TABLE ts_kv ADD COLUMN json_v json;"); |
106 | executeQuery(conn, "ALTER TABLE ts_kv_latest ADD COLUMN json_v json;"); | 105 | executeQuery(conn, "ALTER TABLE ts_kv_latest ADD COLUMN json_v json;"); |
107 | 106 | ||
108 | - log.info("schema timeseries updated!"); | 107 | + log.info("schema timescale updated!"); |
109 | } | 108 | } |
110 | } | 109 | } |
111 | break; | 110 | break; |
@@ -16,16 +16,21 @@ | @@ -16,16 +16,21 @@ | ||
16 | package org.thingsboard.rule.engine.kafka; | 16 | package org.thingsboard.rule.engine.kafka; |
17 | 17 | ||
18 | import lombok.extern.slf4j.Slf4j; | 18 | import lombok.extern.slf4j.Slf4j; |
19 | +import org.apache.commons.lang3.BooleanUtils; | ||
19 | import org.apache.kafka.clients.producer.*; | 20 | import org.apache.kafka.clients.producer.*; |
21 | +import org.apache.kafka.common.header.Headers; | ||
22 | +import org.apache.kafka.common.header.internals.RecordHeader; | ||
23 | +import org.apache.kafka.common.header.internals.RecordHeaders; | ||
20 | import org.thingsboard.rule.engine.api.util.TbNodeUtils; | 24 | import org.thingsboard.rule.engine.api.util.TbNodeUtils; |
21 | import org.thingsboard.rule.engine.api.*; | 25 | import org.thingsboard.rule.engine.api.*; |
22 | import org.thingsboard.server.common.data.plugin.ComponentType; | 26 | import org.thingsboard.server.common.data.plugin.ComponentType; |
23 | import org.thingsboard.server.common.msg.TbMsg; | 27 | import org.thingsboard.server.common.msg.TbMsg; |
24 | import org.thingsboard.server.common.msg.TbMsgMetaData; | 28 | import org.thingsboard.server.common.msg.TbMsgMetaData; |
25 | 29 | ||
30 | +import java.nio.charset.Charset; | ||
31 | +import java.nio.charset.StandardCharsets; | ||
26 | import java.util.Properties; | 32 | import java.util.Properties; |
27 | import java.util.concurrent.ExecutionException; | 33 | import java.util.concurrent.ExecutionException; |
28 | -import java.util.concurrent.atomic.AtomicInteger; | ||
29 | 34 | ||
30 | @Slf4j | 35 | @Slf4j |
31 | @RuleNode( | 36 | @RuleNode( |
@@ -46,8 +51,11 @@ public class TbKafkaNode implements TbNode { | @@ -46,8 +51,11 @@ public class TbKafkaNode implements TbNode { | ||
46 | private static final String PARTITION = "partition"; | 51 | private static final String PARTITION = "partition"; |
47 | private static final String TOPIC = "topic"; | 52 | private static final String TOPIC = "topic"; |
48 | private static final String ERROR = "error"; | 53 | private static final String ERROR = "error"; |
54 | + public static final String TB_MSG_MD_PREFIX = "tb_msg_md_"; | ||
49 | 55 | ||
50 | private TbKafkaNodeConfiguration config; | 56 | private TbKafkaNodeConfiguration config; |
57 | + private boolean addMetadataKeyValuesAsKafkaHeaders; | ||
58 | + private Charset toBytesCharset; | ||
51 | 59 | ||
52 | private Producer<?, String> producer; | 60 | private Producer<?, String> producer; |
53 | 61 | ||
@@ -66,8 +74,10 @@ public class TbKafkaNode implements TbNode { | @@ -66,8 +74,10 @@ public class TbKafkaNode implements TbNode { | ||
66 | properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, config.getBufferMemory()); | 74 | properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, config.getBufferMemory()); |
67 | if (config.getOtherProperties() != null) { | 75 | if (config.getOtherProperties() != null) { |
68 | config.getOtherProperties() | 76 | config.getOtherProperties() |
69 | - .forEach((k,v) -> properties.put(k, v)); | 77 | + .forEach(properties::put); |
70 | } | 78 | } |
79 | + addMetadataKeyValuesAsKafkaHeaders = BooleanUtils.toBooleanDefaultIfNull(config.isAddMetadataKeyValuesAsKafkaHeaders(), false); | ||
80 | + toBytesCharset = config.getKafkaHeadersCharset() != null ? Charset.forName(config.getKafkaHeadersCharset()) : StandardCharsets.UTF_8; | ||
71 | try { | 81 | try { |
72 | this.producer = new KafkaProducer<>(properties); | 82 | this.producer = new KafkaProducer<>(properties); |
73 | } catch (Exception e) { | 83 | } catch (Exception e) { |
@@ -79,16 +89,16 @@ public class TbKafkaNode implements TbNode { | @@ -79,16 +89,16 @@ public class TbKafkaNode implements TbNode { | ||
79 | public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { | 89 | public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { |
80 | String topic = TbNodeUtils.processPattern(config.getTopicPattern(), msg.getMetaData()); | 90 | String topic = TbNodeUtils.processPattern(config.getTopicPattern(), msg.getMetaData()); |
81 | try { | 91 | try { |
82 | - producer.send(new ProducerRecord<>(topic, msg.getData()), | ||
83 | - (metadata, e) -> { | ||
84 | - if (metadata != null) { | ||
85 | - TbMsg next = processResponse(ctx, msg, metadata); | ||
86 | - ctx.tellNext(next, TbRelationTypes.SUCCESS); | ||
87 | - } else { | ||
88 | - TbMsg next = processException(ctx, msg, e); | ||
89 | - ctx.tellFailure(next, e); | ||
90 | - } | ||
91 | - }); | 92 | + if (!addMetadataKeyValuesAsKafkaHeaders) { |
93 | + producer.send(new ProducerRecord<>(topic, msg.getData()), | ||
94 | + (metadata, e) -> processRecord(ctx, msg, metadata, e)); | ||
95 | + } else { | ||
96 | + Headers headers = new RecordHeaders(); | ||
97 | + msg.getMetaData().values().forEach((key, value) -> headers.add(new RecordHeader(TB_MSG_MD_PREFIX + key, value.getBytes(toBytesCharset)))); | ||
98 | + producer.send(new ProducerRecord<>(topic, null, null, null, msg.getData(), headers), | ||
99 | + (metadata, e) -> processRecord(ctx, msg, metadata, e)); | ||
100 | + } | ||
101 | + | ||
92 | } catch (Exception e) { | 102 | } catch (Exception e) { |
93 | ctx.tellFailure(msg, e); | 103 | ctx.tellFailure(msg, e); |
94 | } | 104 | } |
@@ -105,6 +115,16 @@ public class TbKafkaNode implements TbNode { | @@ -105,6 +115,16 @@ public class TbKafkaNode implements TbNode { | ||
105 | } | 115 | } |
106 | } | 116 | } |
107 | 117 | ||
118 | + private void processRecord(TbContext ctx, TbMsg msg, RecordMetadata metadata, Exception e) { | ||
119 | + if (metadata != null) { | ||
120 | + TbMsg next = processResponse(ctx, msg, metadata); | ||
121 | + ctx.tellNext(next, TbRelationTypes.SUCCESS); | ||
122 | + } else { | ||
123 | + TbMsg next = processException(ctx, msg, e); | ||
124 | + ctx.tellFailure(next, e); | ||
125 | + } | ||
126 | + } | ||
127 | + | ||
108 | private TbMsg processResponse(TbContext ctx, TbMsg origMsg, RecordMetadata recordMetadata) { | 128 | private TbMsg processResponse(TbContext ctx, TbMsg origMsg, RecordMetadata recordMetadata) { |
109 | TbMsgMetaData metaData = origMsg.getMetaData().copy(); | 129 | TbMsgMetaData metaData = origMsg.getMetaData().copy(); |
110 | metaData.putValue(OFFSET, String.valueOf(recordMetadata.offset())); | 130 | metaData.putValue(OFFSET, String.valueOf(recordMetadata.offset())); |
@@ -36,6 +36,9 @@ public class TbKafkaNodeConfiguration implements NodeConfiguration<TbKafkaNodeCo | @@ -36,6 +36,9 @@ public class TbKafkaNodeConfiguration implements NodeConfiguration<TbKafkaNodeCo | ||
36 | private String valueSerializer; | 36 | private String valueSerializer; |
37 | private Map<String, String> otherProperties; | 37 | private Map<String, String> otherProperties; |
38 | 38 | ||
39 | + private boolean addMetadataKeyValuesAsKafkaHeaders; | ||
40 | + private String kafkaHeadersCharset; | ||
41 | + | ||
39 | @Override | 42 | @Override |
40 | public TbKafkaNodeConfiguration defaultConfiguration() { | 43 | public TbKafkaNodeConfiguration defaultConfiguration() { |
41 | TbKafkaNodeConfiguration configuration = new TbKafkaNodeConfiguration(); | 44 | TbKafkaNodeConfiguration configuration = new TbKafkaNodeConfiguration(); |
@@ -49,6 +52,8 @@ public class TbKafkaNodeConfiguration implements NodeConfiguration<TbKafkaNodeCo | @@ -49,6 +52,8 @@ public class TbKafkaNodeConfiguration implements NodeConfiguration<TbKafkaNodeCo | ||
49 | configuration.setKeySerializer(StringSerializer.class.getName()); | 52 | configuration.setKeySerializer(StringSerializer.class.getName()); |
50 | configuration.setValueSerializer(StringSerializer.class.getName()); | 53 | configuration.setValueSerializer(StringSerializer.class.getName()); |
51 | configuration.setOtherProperties(Collections.emptyMap()); | 54 | configuration.setOtherProperties(Collections.emptyMap()); |
55 | + configuration.setAddMetadataKeyValuesAsKafkaHeaders(false); | ||
56 | + configuration.setKafkaHeadersCharset("UTF-8"); | ||
52 | return configuration; | 57 | return configuration; |
53 | } | 58 | } |
54 | } | 59 | } |
@@ -51,6 +51,10 @@ let defaultDigitalGaugeOptions = Object.assign({}, canvasGauges.GenericOptions, | @@ -51,6 +51,10 @@ let defaultDigitalGaugeOptions = Object.assign({}, canvasGauges.GenericOptions, | ||
51 | 51 | ||
52 | neonGlowBrightness: 0, | 52 | neonGlowBrightness: 0, |
53 | 53 | ||
54 | + colorTicks: 'gray', | ||
55 | + tickWidth: 4, | ||
56 | + ticks: [], | ||
57 | + | ||
54 | isMobile: false | 58 | isMobile: false |
55 | 59 | ||
56 | }); | 60 | }); |
@@ -133,6 +137,13 @@ export default class CanvasDigitalGauge extends canvasGauges.BaseGauge { | @@ -133,6 +137,13 @@ export default class CanvasDigitalGauge extends canvasGauges.BaseGauge { | ||
133 | } | 137 | } |
134 | } | 138 | } |
135 | 139 | ||
140 | + options.ticksValue = []; | ||
141 | + for(let i = 0; i < options.ticks.length; i++){ | ||
142 | + if(options.ticks[i] !== null){ | ||
143 | + options.ticksValue.push(CanvasDigitalGauge.normalizeValue(options.ticks[i], options.minValue, options.maxValue)) | ||
144 | + } | ||
145 | + } | ||
146 | + | ||
136 | if (options.neonGlowBrightness) { | 147 | if (options.neonGlowBrightness) { |
137 | options.neonColorTitle = tinycolor(options.colorTitle).brighten(options.neonGlowBrightness).toHexString(); | 148 | options.neonColorTitle = tinycolor(options.colorTitle).brighten(options.neonGlowBrightness).toHexString(); |
138 | options.neonColorLabel = tinycolor(options.colorLabel).brighten(options.neonGlowBrightness).toHexString(); | 149 | options.neonColorLabel = tinycolor(options.colorLabel).brighten(options.neonGlowBrightness).toHexString(); |
@@ -729,6 +740,48 @@ function drawBarGlow(context, startX, startY, endX, endY, color, strokeWidth, is | @@ -729,6 +740,48 @@ function drawBarGlow(context, startX, startY, endX, endY, color, strokeWidth, is | ||
729 | context.stroke(); | 740 | context.stroke(); |
730 | } | 741 | } |
731 | 742 | ||
743 | +function drawTickArc(context, tickValues, Cx, Cy, Ri, Rm, Ro, startAngle, endAngle, color, tickWidth) { | ||
744 | + if(!tickValues.length) { | ||
745 | + return; | ||
746 | + } | ||
747 | + | ||
748 | + const strokeWidth = Ro - Ri; | ||
749 | + context.beginPath(); | ||
750 | + context.lineWidth = tickWidth; | ||
751 | + context.strokeStyle = color; | ||
752 | + for (let i = 0; i < tickValues.length; i++) { | ||
753 | + var angle = startAngle + tickValues[i] * endAngle; | ||
754 | + var x1 = Cx + (Ri + strokeWidth) * Math.cos(angle); | ||
755 | + var y1 = Cy + (Ri + strokeWidth) * Math.sin(angle); | ||
756 | + var x2 = Cx + Ri * Math.cos(angle); | ||
757 | + var y2 = Cy + Ri * Math.sin(angle); | ||
758 | + context.moveTo(x1, y1); | ||
759 | + context.lineTo(x2, y2); | ||
760 | + } | ||
761 | + context.stroke(); | ||
762 | +} | ||
763 | + | ||
764 | +function drawTickBar(context, tickValues, startX, startY, distanceBar, strokeWidth, isVertical, color, tickWidth) { | ||
765 | + if(!tickValues.length) { | ||
766 | + return; | ||
767 | + } | ||
768 | + | ||
769 | + context.beginPath(); | ||
770 | + context.lineWidth = tickWidth; | ||
771 | + context.strokeStyle = color; | ||
772 | + for (let i = 0; i < tickValues.length; i++) { | ||
773 | + let tickValue = tickValues[i] * distanceBar; | ||
774 | + if (isVertical) { | ||
775 | + context.moveTo(startX - strokeWidth / 2, startY + tickValue - distanceBar); | ||
776 | + context.lineTo(startX + strokeWidth / 2, startY + tickValue - distanceBar); | ||
777 | + } else { | ||
778 | + context.moveTo(startX + tickValue, startY); | ||
779 | + context.lineTo(startX + tickValue, startY + strokeWidth); | ||
780 | + } | ||
781 | + } | ||
782 | + context.stroke(); | ||
783 | +} | ||
784 | + | ||
732 | function drawProgress(context, options, progress) { | 785 | function drawProgress(context, options, progress) { |
733 | var neonColor; | 786 | var neonColor; |
734 | if (options.neonGlowBrightness) { | 787 | if (options.neonGlowBrightness) { |
@@ -759,6 +812,7 @@ function drawProgress(context, options, progress) { | @@ -759,6 +812,7 @@ function drawProgress(context, options, progress) { | ||
759 | if (options.neonGlowBrightness && !options.isMobile) { | 812 | if (options.neonGlowBrightness && !options.isMobile) { |
760 | drawArcGlow(context, Cx, Cy, Ri, Rm, Ro, neonColor, progress, true, options.donutStartAngle, options.donutEndAngle); | 813 | drawArcGlow(context, Cx, Cy, Ri, Rm, Ro, neonColor, progress, true, options.donutStartAngle, options.donutEndAngle); |
761 | } | 814 | } |
815 | + drawTickArc(context, options.ticksValue, Cx, Cy, Ri, Rm, Ro, options.donutStartAngle, options.donutEndAngle - options.donutStartAngle, options.colorTicks, options.tickWidth); | ||
762 | } else if (options.gaugeType === 'arc') { | 816 | } else if (options.gaugeType === 'arc') { |
763 | if (options.neonGlowBrightness) { | 817 | if (options.neonGlowBrightness) { |
764 | context.strokeStyle = neonColor; | 818 | context.strokeStyle = neonColor; |
@@ -769,6 +823,7 @@ function drawProgress(context, options, progress) { | @@ -769,6 +823,7 @@ function drawProgress(context, options, progress) { | ||
769 | if (options.neonGlowBrightness && !options.isMobile) { | 823 | if (options.neonGlowBrightness && !options.isMobile) { |
770 | drawArcGlow(context, Cx, Cy, Ri, Rm, Ro, neonColor, progress, false); | 824 | drawArcGlow(context, Cx, Cy, Ri, Rm, Ro, neonColor, progress, false); |
771 | } | 825 | } |
826 | + drawTickArc(context, options.ticksValue, Cx, Cy, Ri, Rm, Ro, Math.PI, Math.PI, options.colorTicks, options.tickWidth); | ||
772 | } else if (options.gaugeType === 'horizontalBar') { | 827 | } else if (options.gaugeType === 'horizontalBar') { |
773 | if (options.neonGlowBrightness) { | 828 | if (options.neonGlowBrightness) { |
774 | context.strokeStyle = neonColor; | 829 | context.strokeStyle = neonColor; |
@@ -781,6 +836,7 @@ function drawProgress(context, options, progress) { | @@ -781,6 +836,7 @@ function drawProgress(context, options, progress) { | ||
781 | drawBarGlow(context, barLeft, barTop + strokeWidth/2, barLeft + (barRight-barLeft)*progress, barTop + strokeWidth/2, | 836 | drawBarGlow(context, barLeft, barTop + strokeWidth/2, barLeft + (barRight-barLeft)*progress, barTop + strokeWidth/2, |
782 | neonColor, strokeWidth, false); | 837 | neonColor, strokeWidth, false); |
783 | } | 838 | } |
839 | + drawTickBar(context, options.ticksValue, barLeft, barTop, barRight - barLeft, strokeWidth, false, options.colorTicks, options.tickWidth); | ||
784 | } else if (options.gaugeType === 'verticalBar') { | 840 | } else if (options.gaugeType === 'verticalBar') { |
785 | if (options.neonGlowBrightness) { | 841 | if (options.neonGlowBrightness) { |
786 | context.strokeStyle = neonColor; | 842 | context.strokeStyle = neonColor; |
@@ -793,6 +849,7 @@ function drawProgress(context, options, progress) { | @@ -793,6 +849,7 @@ function drawProgress(context, options, progress) { | ||
793 | drawBarGlow(context, baseX + width/2, barBottom, baseX + width/2, barBottom - (barBottom-barTop)*progress, | 849 | drawBarGlow(context, baseX + width/2, barBottom, baseX + width/2, barBottom - (barBottom-barTop)*progress, |
794 | neonColor, strokeWidth, true); | 850 | neonColor, strokeWidth, true); |
795 | } | 851 | } |
852 | + drawTickBar(context, options.ticksValue, baseX + width / 2, barTop, barTop - barBottom, strokeWidth, true, options.colorTicks, options.tickWidth); | ||
796 | } | 853 | } |
797 | } | 854 | } |
798 | 855 |
@@ -62,6 +62,12 @@ export default class TbCanvasDigitalGauge { | @@ -62,6 +62,12 @@ export default class TbCanvasDigitalGauge { | ||
62 | this.localSettings.fixedLevelColors = settings.fixedLevelColors || []; | 62 | this.localSettings.fixedLevelColors = settings.fixedLevelColors || []; |
63 | } | 63 | } |
64 | 64 | ||
65 | + this.localSettings.showTicks = settings.showTicks || false; | ||
66 | + this.localSettings.ticks = []; | ||
67 | + this.localSettings.ticksValue = settings.ticksValue || []; | ||
68 | + this.localSettings.tickWidth = settings.tickWidth || 4; | ||
69 | + this.localSettings.colorTicks = settings.colorTicks || '#666'; | ||
70 | + | ||
65 | this.localSettings.decimals = angular.isDefined(dataKey.decimals) ? dataKey.decimals : | 71 | this.localSettings.decimals = angular.isDefined(dataKey.decimals) ? dataKey.decimals : |
66 | ((angular.isDefined(settings.decimals) && settings.decimals !== null) | 72 | ((angular.isDefined(settings.decimals) && settings.decimals !== null) |
67 | ? settings.decimals : ctx.decimals); | 73 | ? settings.decimals : ctx.decimals); |
@@ -145,6 +151,10 @@ export default class TbCanvasDigitalGauge { | @@ -145,6 +151,10 @@ export default class TbCanvasDigitalGauge { | ||
145 | gaugeColor: this.localSettings.gaugeColor, | 151 | gaugeColor: this.localSettings.gaugeColor, |
146 | levelColors: this.localSettings.levelColors, | 152 | levelColors: this.localSettings.levelColors, |
147 | 153 | ||
154 | + colorTicks: this.localSettings.colorTicks, | ||
155 | + tickWidth: this.localSettings.tickWidth, | ||
156 | + ticks: this.localSettings.ticks, | ||
157 | + | ||
148 | title: this.localSettings.title, | 158 | title: this.localSettings.title, |
149 | 159 | ||
150 | fontTitleSize: this.localSettings.titleFont.size, | 160 | fontTitleSize: this.localSettings.titleFont.size, |
@@ -204,9 +214,81 @@ export default class TbCanvasDigitalGauge { | @@ -204,9 +214,81 @@ export default class TbCanvasDigitalGauge { | ||
204 | if (this.localSettings.useFixedLevelColor) { | 214 | if (this.localSettings.useFixedLevelColor) { |
205 | if (this.localSettings.fixedLevelColors && this.localSettings.fixedLevelColors.length > 0) { | 215 | if (this.localSettings.fixedLevelColors && this.localSettings.fixedLevelColors.length > 0) { |
206 | this.localSettings.levelColors = this.settingLevelColorsSubscribe(this.localSettings.fixedLevelColors); | 216 | this.localSettings.levelColors = this.settingLevelColorsSubscribe(this.localSettings.fixedLevelColors); |
207 | - this.updateLevelColors(this.localSettings.levelColors); | ||
208 | } | 217 | } |
209 | } | 218 | } |
219 | + if (this.localSettings.showTicks) { | ||
220 | + if (this.localSettings.ticksValue && this.localSettings.ticksValue.length) { | ||
221 | + this.localSettings.ticks = this.settingTicksSubscribe(this.localSettings.ticksValue); | ||
222 | + } | ||
223 | + } | ||
224 | + this.updateSetting(); | ||
225 | + } | ||
226 | + | ||
227 | + static generateDatasorce(ctx, datasources, entityAlias, attribute, settings){ | ||
228 | + let entityAliasId = ctx.aliasController.getEntityAliasId(entityAlias); | ||
229 | + if (!entityAliasId) { | ||
230 | + throw new Error('Not valid entity aliase name ' + entityAlias); | ||
231 | + } | ||
232 | + | ||
233 | + let datasource = datasources.filter((datasource) => { | ||
234 | + return datasource.entityAliasId === entityAliasId; | ||
235 | + })[0]; | ||
236 | + | ||
237 | + let dataKey = { | ||
238 | + type: ctx.$scope.$injector.get('types').dataKeyType.attribute, | ||
239 | + name: attribute, | ||
240 | + label: attribute, | ||
241 | + settings: [settings], | ||
242 | + _hash: Math.random() | ||
243 | + }; | ||
244 | + | ||
245 | + if (datasource) { | ||
246 | + let findDataKey = datasource.dataKeys.filter((dataKey) => { | ||
247 | + return dataKey.name === attribute; | ||
248 | + })[0]; | ||
249 | + | ||
250 | + if (findDataKey) { | ||
251 | + findDataKey.settings.push(settings); | ||
252 | + } else { | ||
253 | + datasource.dataKeys.push(dataKey) | ||
254 | + } | ||
255 | + } else { | ||
256 | + datasource = { | ||
257 | + type: ctx.$scope.$injector.get('types').datasourceType.entity, | ||
258 | + name: entityAlias, | ||
259 | + aliasName: entityAlias, | ||
260 | + entityAliasId: entityAliasId, | ||
261 | + dataKeys: [dataKey] | ||
262 | + }; | ||
263 | + datasources.push(datasource); | ||
264 | + } | ||
265 | + | ||
266 | + return datasources; | ||
267 | + } | ||
268 | + | ||
269 | + settingTicksSubscribe(options) { | ||
270 | + let ticksDatasource = []; | ||
271 | + let predefineTicks = []; | ||
272 | + | ||
273 | + for (let i = 0; i < options.length; i++) { | ||
274 | + let tick = options[i]; | ||
275 | + if (tick.valueSource === 'predefinedValue' && isFinite(tick.value)) { | ||
276 | + predefineTicks.push(tick.value) | ||
277 | + } else if (tick.entityAlias && tick.attribute) { | ||
278 | + try { | ||
279 | + ticksDatasource = TbCanvasDigitalGauge.generateDatasorce(this.ctx, ticksDatasource, tick.entityAlias, tick.attribute, predefineTicks.length); | ||
280 | + } catch (e) { | ||
281 | + continue; | ||
282 | + } | ||
283 | + predefineTicks.push(null); | ||
284 | + } | ||
285 | + } | ||
286 | + | ||
287 | + this.subscribeAttributes(ticksDatasource, 'ticks').then((subscription) => { | ||
288 | + this.ticksSourcesSubscription = subscription; | ||
289 | + }); | ||
290 | + | ||
291 | + return predefineTicks; | ||
210 | } | 292 | } |
211 | 293 | ||
212 | settingLevelColorsSubscribe(options) { | 294 | settingLevelColorsSubscribe(options) { |
@@ -220,50 +302,14 @@ export default class TbCanvasDigitalGauge { | @@ -220,50 +302,14 @@ export default class TbCanvasDigitalGauge { | ||
220 | color: color | 302 | color: color |
221 | }) | 303 | }) |
222 | } else if (levelSetting.entityAlias && levelSetting.attribute) { | 304 | } else if (levelSetting.entityAlias && levelSetting.attribute) { |
223 | - let entityAliasId = this.ctx.aliasController.getEntityAliasId(levelSetting.entityAlias); | ||
224 | - if (!entityAliasId) { | ||
225 | - return; | ||
226 | - } | ||
227 | - | ||
228 | - let datasource = levelColorsDatasource.filter((datasource) => { | ||
229 | - return datasource.entityAliasId === entityAliasId; | ||
230 | - })[0]; | ||
231 | - | ||
232 | - let dataKey = { | ||
233 | - type: this.ctx.$scope.$injector.get('types').dataKeyType.attribute, | ||
234 | - name: levelSetting.attribute, | ||
235 | - label: levelSetting.attribute, | ||
236 | - settings: [{ | 305 | + try { |
306 | + levelColorsDatasource = TbCanvasDigitalGauge.generateDatasorce(this.ctx, levelColorsDatasource, levelSetting.entityAlias, levelSetting.attribute, { | ||
237 | color: color, | 307 | color: color, |
238 | index: predefineLevelColors.length | 308 | index: predefineLevelColors.length |
239 | - }], | ||
240 | - _hash: Math.random() | ||
241 | - }; | ||
242 | - | ||
243 | - if (datasource) { | ||
244 | - let findDataKey = datasource.dataKeys.filter((dataKey) => { | ||
245 | - return dataKey.name === levelSetting.attribute; | ||
246 | - })[0]; | ||
247 | - | ||
248 | - if (findDataKey) { | ||
249 | - findDataKey.settings.push({ | ||
250 | - color: color, | ||
251 | - index: predefineLevelColors.length | ||
252 | - }); | ||
253 | - } else { | ||
254 | - datasource.dataKeys.push(dataKey) | ||
255 | - } | ||
256 | - } else { | ||
257 | - datasource = { | ||
258 | - type: this.ctx.$scope.$injector.get('types').datasourceType.entity, | ||
259 | - name: levelSetting.entityAlias, | ||
260 | - aliasName: levelSetting.entityAlias, | ||
261 | - entityAliasId: entityAliasId, | ||
262 | - dataKeys: [dataKey] | ||
263 | - }; | ||
264 | - levelColorsDatasource.push(datasource); | 309 | + }); |
310 | + } catch (e) { | ||
311 | + return; | ||
265 | } | 312 | } |
266 | - | ||
267 | predefineLevelColors.push(null); | 313 | predefineLevelColors.push(null); |
268 | } | 314 | } |
269 | } | 315 | } |
@@ -278,49 +324,63 @@ export default class TbCanvasDigitalGauge { | @@ -278,49 +324,63 @@ export default class TbCanvasDigitalGauge { | ||
278 | } | 324 | } |
279 | } | 325 | } |
280 | 326 | ||
281 | - this.subscribeLevelColorsAttributes(levelColorsDatasource); | 327 | + this.subscribeAttributes(levelColorsDatasource, 'levelColors').then((subscription) => { |
328 | + this.levelColorSourcesSubscription = subscription; | ||
329 | + }); | ||
282 | 330 | ||
283 | return predefineLevelColors; | 331 | return predefineLevelColors; |
284 | } | 332 | } |
285 | 333 | ||
286 | - updateLevelColors(levelColors) { | ||
287 | - this.gauge.options.levelColors = levelColors; | ||
288 | - this.gauge.options = CanvasDigitalGauge.configure(this.gauge.options); | ||
289 | - this.gauge.update(); | ||
290 | - } | 334 | + subscribeAttributes(datasources, typeAttributes) { |
335 | + if (!datasources.length) { | ||
336 | + return this.ctx.$scope.$injector.get('$q').when(null); | ||
337 | + } | ||
291 | 338 | ||
292 | - subscribeLevelColorsAttributes(datasources) { | ||
293 | - let TbCanvasDigitalGauge = this; | ||
294 | let levelColorsSourcesSubscriptionOptions = { | 339 | let levelColorsSourcesSubscriptionOptions = { |
295 | datasources: datasources, | 340 | datasources: datasources, |
296 | useDashboardTimewindow: false, | 341 | useDashboardTimewindow: false, |
297 | type: this.ctx.$scope.$injector.get('types').widgetType.latest.value, | 342 | type: this.ctx.$scope.$injector.get('types').widgetType.latest.value, |
298 | callbacks: { | 343 | callbacks: { |
299 | onDataUpdated: (subscription) => { | 344 | onDataUpdated: (subscription) => { |
300 | - for (let i = 0; i < subscription.data.length; i++) { | ||
301 | - let keyData = subscription.data[i]; | ||
302 | - if (keyData && keyData.data && keyData.data[0]) { | ||
303 | - let attrValue = keyData.data[0][1]; | ||
304 | - if (isFinite(attrValue)) { | ||
305 | - for (let i = 0; i < keyData.dataKey.settings.length; i++) { | ||
306 | - let setting = keyData.dataKey.settings[i]; | ||
307 | - this.localSettings.levelColors[setting.index] = { | ||
308 | - value: attrValue, | ||
309 | - color: setting.color | ||
310 | - }; | ||
311 | - } | ||
312 | - } | ||
313 | - } | ||
314 | - } | ||
315 | - this.updateLevelColors(this.localSettings.levelColors); | 345 | + this.updateAttribute(subscription.data, typeAttributes); |
316 | } | 346 | } |
317 | } | 347 | } |
318 | }; | 348 | }; |
319 | - this.ctx.subscriptionApi.createSubscription(levelColorsSourcesSubscriptionOptions, true).then( | ||
320 | - (subscription) => { | ||
321 | - TbCanvasDigitalGauge.levelColorSourcesSubscription = subscription; | 349 | + |
350 | + return this.ctx.subscriptionApi.createSubscription(levelColorsSourcesSubscriptionOptions, true); | ||
351 | + } | ||
352 | + | ||
353 | + updateAttribute(data, typeAttributes) { | ||
354 | + for (let i = 0; i < data.length; i++) { | ||
355 | + let keyData = data[i]; | ||
356 | + if (keyData && keyData.data && keyData.data[0]) { | ||
357 | + let attrValue = keyData.data[0][1]; | ||
358 | + if (isFinite(attrValue)) { | ||
359 | + for (let i = 0; i < keyData.dataKey.settings.length; i++) { | ||
360 | + let setting = keyData.dataKey.settings[i]; | ||
361 | + switch (typeAttributes) { | ||
362 | + case 'levelColors': | ||
363 | + this.localSettings.levelColors[setting.index] = { | ||
364 | + value: attrValue, | ||
365 | + color: setting.color | ||
366 | + }; | ||
367 | + break; | ||
368 | + case 'ticks': | ||
369 | + this.localSettings.ticks[setting] = attrValue; | ||
370 | + break; | ||
371 | + } | ||
372 | + } | ||
373 | + } | ||
322 | } | 374 | } |
323 | - ); | 375 | + } |
376 | + this.updateSetting(); | ||
377 | + } | ||
378 | + | ||
379 | + updateSetting() { | ||
380 | + this.gauge.options.ticks = this.localSettings.ticks; | ||
381 | + this.gauge.options.levelColors = this.localSettings.levelColors; | ||
382 | + this.gauge.options = CanvasDigitalGauge.configure(this.gauge.options); | ||
383 | + this.gauge.update(); | ||
324 | } | 384 | } |
325 | 385 | ||
326 | update() { | 386 | update() { |
@@ -526,6 +586,48 @@ export default class TbCanvasDigitalGauge { | @@ -526,6 +586,48 @@ export default class TbCanvasDigitalGauge { | ||
526 | } | 586 | } |
527 | } | 587 | } |
528 | }, | 588 | }, |
589 | + "showTicks": { | ||
590 | + "title": "Show ticks", | ||
591 | + "type": "boolean", | ||
592 | + "default": false | ||
593 | + }, | ||
594 | + "tickWidth": { | ||
595 | + "title": "Width ticks", | ||
596 | + "type": "number", | ||
597 | + "default": 4 | ||
598 | + }, | ||
599 | + "colorTicks": { | ||
600 | + "title": "Color ticks", | ||
601 | + "type": "string", | ||
602 | + "default": "#666" | ||
603 | + }, | ||
604 | + "ticksValue": { | ||
605 | + "title": "The ticks predefined value", | ||
606 | + "type": "array", | ||
607 | + "items": { | ||
608 | + "title": "tickValue", | ||
609 | + "type": "object", | ||
610 | + "properties": { | ||
611 | + "valueSource": { | ||
612 | + "title": "Value source", | ||
613 | + "type": "string", | ||
614 | + "default": "predefinedValue" | ||
615 | + }, | ||
616 | + "entityAlias": { | ||
617 | + "title": "Source entity alias", | ||
618 | + "type": "string" | ||
619 | + }, | ||
620 | + "attribute": { | ||
621 | + "title": "Source entity attribute", | ||
622 | + "type": "string" | ||
623 | + }, | ||
624 | + "value": { | ||
625 | + "title": "Value (if predefined value is selected)", | ||
626 | + "type": "number" | ||
627 | + } | ||
628 | + } | ||
629 | + } | ||
630 | + }, | ||
529 | "animation": { | 631 | "animation": { |
530 | "title": "Enable animation", | 632 | "title": "Enable animation", |
531 | "type": "boolean", | 633 | "type": "boolean", |
@@ -771,6 +873,40 @@ export default class TbCanvasDigitalGauge { | @@ -771,6 +873,40 @@ export default class TbCanvasDigitalGauge { | ||
771 | } | 873 | } |
772 | ] | 874 | ] |
773 | }, | 875 | }, |
876 | + "showTicks", | ||
877 | + { | ||
878 | + "key": "tickWidth", | ||
879 | + "condition": "model.showTicks === true" | ||
880 | + }, | ||
881 | + { | ||
882 | + "key": "colorTicks", | ||
883 | + "condition": "model.showTicks === true", | ||
884 | + "type": "color" | ||
885 | + }, | ||
886 | + { | ||
887 | + "key": "ticksValue", | ||
888 | + "condition": "model.showTicks === true", | ||
889 | + "items": [ | ||
890 | + { | ||
891 | + "key": "ticksValue[].valueSource", | ||
892 | + "type": "rc-select", | ||
893 | + "multiple": false, | ||
894 | + "items": [ | ||
895 | + { | ||
896 | + "value": "predefinedValue", | ||
897 | + "label": "Predefined value (Default)" | ||
898 | + }, | ||
899 | + { | ||
900 | + "value": "entityAttribute", | ||
901 | + "label": "Value taken from entity attribute" | ||
902 | + } | ||
903 | + ] | ||
904 | + }, | ||
905 | + "ticksValue[].value", | ||
906 | + "ticksValue[].entityAlias", | ||
907 | + "ticksValue[].attribute" | ||
908 | + ] | ||
909 | + }, | ||
774 | "animation", | 910 | "animation", |
775 | "animationDuration", | 911 | "animationDuration", |
776 | { | 912 | { |