Browse Source

Merge branch 'master' into fix_bug_lwm2m_fota

pull/12876/head
nick 2 years ago
parent
commit
ea4886726a
  1. 1
      README.md
  2. 1551
      application/src/main/data/json/demo/dashboards/thermostats.json
  3. 219
      application/src/main/data/json/system/scada_symbols/cylindrical-tank.svg
  4. 219
      application/src/main/data/json/system/scada_symbols/elevated-tank.svg
  5. 140
      application/src/main/data/json/system/scada_symbols/horizontal-tank-hp.svg
  6. 219
      application/src/main/data/json/system/scada_symbols/horizontal-tank.svg
  7. 219
      application/src/main/data/json/system/scada_symbols/large-cylindrical-tank.svg
  8. 217
      application/src/main/data/json/system/scada_symbols/large-stand-cylindrical-tank.svg
  9. 219
      application/src/main/data/json/system/scada_symbols/large-stand-vertical-tank.svg
  10. 219
      application/src/main/data/json/system/scada_symbols/large-vertical-tank.svg
  11. 4
      application/src/main/data/json/system/scada_symbols/long-bottom-filter.svg
  12. 4
      application/src/main/data/json/system/scada_symbols/long-top-filter.svg
  13. 140
      application/src/main/data/json/system/scada_symbols/pool-hp.svg
  14. 4
      application/src/main/data/json/system/scada_symbols/short-top-filter.svg
  15. 140
      application/src/main/data/json/system/scada_symbols/short-vertical-tank-hp.svg
  16. 219
      application/src/main/data/json/system/scada_symbols/small-cylindrical-tank.svg
  17. 219
      application/src/main/data/json/system/scada_symbols/small-spherical-tank.svg
  18. 219
      application/src/main/data/json/system/scada_symbols/spherical-tank.svg
  19. 219
      application/src/main/data/json/system/scada_symbols/stand-cylindrical-tank.svg
  20. 219
      application/src/main/data/json/system/scada_symbols/stand-horizontal-tank.svg
  21. 219
      application/src/main/data/json/system/scada_symbols/stand-vertical-short-tank.svg
  22. 217
      application/src/main/data/json/system/scada_symbols/stand-vertical-tank.svg
  23. 220
      application/src/main/data/json/system/scada_symbols/vertical-short-tank.svg
  24. 140
      application/src/main/data/json/system/scada_symbols/vertical-tank-hp.svg
  25. 220
      application/src/main/data/json/system/scada_symbols/vertical-tank.svg
  26. 4
      application/src/main/data/json/system/widget_bundles/maps.json
  27. 2
      application/src/main/data/json/system/widget_types/horizontal_ellipse_tank.json
  28. 26
      application/src/main/data/json/system/widget_types/image_map.json
  29. 32
      application/src/main/data/json/system/widget_types/map.json
  30. 36
      application/src/main/data/json/system/widget_types/route_map.json
  31. 37
      application/src/main/data/json/system/widget_types/trip_map.json
  32. 5
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  33. 1
      application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
  34. 39
      application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java
  35. 10
      application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java
  36. 5
      application/src/main/java/org/thingsboard/server/actors/shared/AbstractContextAwareMsgProcessor.java
  37. 6
      application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java
  38. 33
      application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
  39. 51
      application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
  40. 35
      application/src/main/java/org/thingsboard/server/service/cf/AbstractCalculatedFieldStateService.java
  41. 7
      application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldStateService.java
  42. 7
      application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldQueueService.java
  43. 26
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/KafkaCalculatedFieldStateService.java
  44. 21
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/RocksDBCalculatedFieldStateService.java
  45. 57
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCalculatedFieldConsumerService.java
  46. 101
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
  47. 3
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java
  48. 2
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  49. 4
      application/src/main/resources/thingsboard.yml
  50. 74
      application/src/test/java/org/thingsboard/server/actors/service/ComponentActorTest.java
  51. 5
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  52. 2
      application/src/test/java/org/thingsboard/server/controller/EdqsEntityQueryControllerTest.java
  53. 50
      application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java
  54. 10
      application/src/test/java/org/thingsboard/server/edge/AssetProfileEdgeTest.java
  55. 10
      application/src/test/java/org/thingsboard/server/edge/DeviceProfileEdgeTest.java
  56. 4
      application/src/test/java/org/thingsboard/server/service/entitiy/EntityServiceTest.java
  57. 2
      common/cluster-api/src/main/java/org/thingsboard/server/cluster/TbClusterService.java
  58. 11
      common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java
  59. 1
      common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/EntityFields.java
  60. 1
      common/edqs/src/main/java/org/thingsboard/server/edqs/processor/EdqsProcessor.java
  61. 11
      common/edqs/src/main/java/org/thingsboard/server/edqs/state/KafkaEdqsStateService.java
  62. 3
      common/message/src/main/java/org/thingsboard/server/common/msg/cf/CalculatedFieldEntityLifecycleMsg.java
  63. 35
      common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java
  64. 40
      common/proto/src/main/proto/queue.proto
  65. 21
      common/queue/src/main/java/org/thingsboard/server/queue/common/consumer/PartitionedQueueConsumerManager.java
  66. 98
      common/queue/src/main/java/org/thingsboard/server/queue/common/consumer/QueueStateService.java
  67. 2
      common/queue/src/main/java/org/thingsboard/server/queue/common/consumer/QueueTaskType.java
  68. 7
      common/queue/src/main/java/org/thingsboard/server/queue/common/consumer/TbQueueConsumerManagerTask.java
  69. 27
      common/queue/src/main/java/org/thingsboard/server/queue/common/state/DefaultQueueStateService.java
  70. 81
      common/queue/src/main/java/org/thingsboard/server/queue/common/state/KafkaQueueStateService.java
  71. 114
      common/queue/src/main/java/org/thingsboard/server/queue/common/state/QueueStateService.java
  72. 68
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java
  73. 3
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/QueueKey.java
  74. 1
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/event/PartitionChangeEvent.java
  75. 3
      common/queue/src/main/java/org/thingsboard/server/queue/edqs/EdqsQueueFactory.java
  76. 7
      common/queue/src/main/java/org/thingsboard/server/queue/edqs/InMemoryEdqsQueueFactory.java
  77. 6
      common/queue/src/main/java/org/thingsboard/server/queue/edqs/KafkaEdqsQueueFactory.java
  78. 2
      common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaAdmin.java
  79. 2
      common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java
  80. 5
      common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java
  81. 5
      common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java
  82. 5
      common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java
  83. 3
      common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineQueueFactory.java
  84. 53
      common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageReportClient.java
  85. 3
      dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java
  86. 4
      dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java
  87. 38
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java
  88. 368
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/cf/CalculatedFieldTest.java
  89. 2
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java
  90. 6
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java
  91. 8
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java
  92. 4
      msa/vc-executor/src/main/resources/tb-vc-executor.yml
  93. 4
      transport/coap/src/main/resources/tb-coap-transport.yml
  94. 4
      transport/http/src/main/resources/tb-http-transport.yml
  95. 4
      transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml
  96. 4
      transport/mqtt/src/main/resources/tb-mqtt-transport.yml
  97. 4
      transport/snmp/src/main/resources/tb-snmp-transport.yml
  98. 2
      ui-ngx/package.json
  99. 5
      ui-ngx/src/app/core/http/entity.service.ts
  100. 90
      ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts

1
README.md

@ -32,7 +32,6 @@ Collect and Visualize your IoT data in minutes by following this [guide](https:/
## Support ## Support
- [Q&A forum](https://groups.google.com/forum/#!forum/thingsboard)
- [Stackoverflow](http://stackoverflow.com/questions/tagged/thingsboard) - [Stackoverflow](http://stackoverflow.com/questions/tagged/thingsboard)
## Licenses ## Licenses

1551
application/src/main/data/json/demo/dashboards/thermostats.json

File diff suppressed because it is too large

219
application/src/main/data/json/system/scada_symbols/cylindrical-tank.svg

@ -33,7 +33,7 @@
}, },
{ {
"tag": "scale", "tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 205;\n var majorIntervalLength = 760 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(340, y, 372, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 330, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(352, minorY, 372, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 205;\n var majorIntervalLength = 760 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n var tankCapacity = ctx.properties.scaleDisplayFormat ? 100 : (ctx.values.tankCapacity || 100);\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(340, y, 372, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = ctx.api.formatValue((tankCapacity - i * (tankCapacity/majorIntervals)).toFixed(0), 0, ctx.properties.majorUnits, false);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 330, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(352, minorY, 372, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null "actions": null
}, },
{ {
@ -278,80 +278,43 @@
"name": "{i18n:scada.symbol.tank-color}", "name": "{i18n:scada.symbol.tank-color}",
"type": "color", "type": "color",
"default": "#E5E5E5", "default": "#E5E5E5",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "fluidColor", "id": "fluidColor",
"name": "{i18n:scada.symbol.fluid-color}", "name": "{i18n:scada.symbol.fluid-color}",
"type": "color", "type": "color",
"default": "#1EC1F480", "default": "#1EC1F480",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueBox", "id": "valueBox",
"name": "{i18n:scada.symbol.value-box}", "name": "{i18n:scada.symbol.value-box}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueBoxColor", "id": "valueBoxColor",
"name": "{i18n:scada.symbol.value-box}", "name": "{i18n:scada.symbol.value-box}",
"type": "color", "type": "color",
"default": "#F3F3F3", "default": "#F3F3F3",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": true,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueUnits", "id": "valueUnits",
"name": "{i18n:scada.symbol.value-text}", "name": "{i18n:scada.symbol.value-text}",
"type": "units", "type": "units",
"default": "gal", "default": "gal",
"required": null,
"subLabel": "{i18n:scada.symbol.units}", "subLabel": "{i18n:scada.symbol.units}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": true,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueTextFont", "id": "valueTextFont",
@ -364,80 +327,78 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": true,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueTextColor", "id": "valueTextColor",
"name": "{i18n:scada.symbol.value-text}", "name": "{i18n:scada.symbol.value-text}",
"type": "color", "type": "color",
"default": "#0000008A", "default": "#0000008A",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": true,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "scale", "id": "scale",
"name": "{i18n:scada.symbol.scale}", "name": "{i18n:scada.symbol.scale}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null, },
"fieldSuffix": null, {
"disableOnProperty": null, "id": "scaleDisplayFormat",
"rowClass": "", "name": "{i18n:scada.symbol.scale}",
"fieldClass": "", "type": "select",
"min": null, "default": true,
"max": null, "subLabel": "{i18n:scada.symbol.display-format}",
"step": null "disableOnProperty": "scale",
"items": [
{
"value": true,
"label": "Percentage"
},
{
"value": false,
"label": "Absolute"
}
],
"disabled": false,
"visible": true
}, },
{ {
"id": "transparent", "id": "transparent",
"name": "{i18n:scada.symbol.transparent-mode}", "name": "{i18n:scada.symbol.transparent-mode}",
"type": "switch", "type": "switch",
"default": false, "default": false,
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorIntervals", "id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}", "name": "{i18n:scada.symbol.major-ticks}",
"type": "number", "type": "number",
"default": 10, "default": 10,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "step": 1,
"step": 1 "disabled": false,
"visible": true
},
{
"id": "majorUnits",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "units",
"subLabel": "{i18n:scada.symbol.units}",
"divider": true,
"disableOnProperty": "scale",
"disabled": false,
"visible": true
}, },
{ {
"id": "majorFont", "id": "majorFont",
@ -450,128 +411,84 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorColor", "id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#00000061", "default": "#00000061",
"required": null,
"subLabel": "{i18n:scada.symbol.normal}", "subLabel": "{i18n:scada.symbol.normal}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorWarningColor", "id": "majorWarningColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorCriticalColor", "id": "majorCriticalColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorIntervals", "id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}", "name": "{i18n:scada.symbol.minor-ticks}",
"type": "number", "type": "number",
"default": 5, "default": 5,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "disabled": false,
"step": null "visible": true
}, },
{ {
"id": "minorColor", "id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#0000001F", "default": "#0000001F",
"required": null,
"subLabel": "{i18n:scada.symbol.normal}", "subLabel": "{i18n:scada.symbol.normal}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorWarningColor", "id": "minorWarningColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorCriticalColor", "id": "minorCriticalColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
} }
] ]
}]]></tb:metadata> }]]></tb:metadata>

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 111 KiB

219
application/src/main/data/json/system/scada_symbols/elevated-tank.svg

@ -34,7 +34,7 @@
}, },
{ {
"tag": "scale", "tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 265;\n var majorIntervalLength = 895 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(825, y, 857, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 815, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(837, minorY, 857, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 265;\n var majorIntervalLength = 895 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n var tankCapacity = ctx.properties.scaleDisplayFormat ? 100 : (ctx.values.tankCapacity || 100);\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(825, y, 857, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = ctx.api.formatValue((tankCapacity - i * (tankCapacity/majorIntervals)).toFixed(0), 0, ctx.properties.majorUnits, false);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 815, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(837, minorY, 857, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null "actions": null
}, },
{ {
@ -274,80 +274,43 @@
"name": "{i18n:scada.symbol.tank-color}", "name": "{i18n:scada.symbol.tank-color}",
"type": "color", "type": "color",
"default": "#E5E5E5", "default": "#E5E5E5",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "fluidColor", "id": "fluidColor",
"name": "{i18n:scada.symbol.fluid-color}", "name": "{i18n:scada.symbol.fluid-color}",
"type": "color", "type": "color",
"default": "#1EC1F480", "default": "#1EC1F480",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueBox", "id": "valueBox",
"name": "{i18n:scada.symbol.value-box}", "name": "{i18n:scada.symbol.value-box}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueBoxColor", "id": "valueBoxColor",
"name": "{i18n:scada.symbol.value-box}", "name": "{i18n:scada.symbol.value-box}",
"type": "color", "type": "color",
"default": "#F3F3F3", "default": "#F3F3F3",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueUnits", "id": "valueUnits",
"name": "{i18n:scada.symbol.value-text}", "name": "{i18n:scada.symbol.value-text}",
"type": "units", "type": "units",
"default": "gal", "default": "gal",
"required": null,
"subLabel": "{i18n:scada.symbol.units}", "subLabel": "{i18n:scada.symbol.units}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueTextFont", "id": "valueTextFont",
@ -360,80 +323,78 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueTextColor", "id": "valueTextColor",
"name": "{i18n:scada.symbol.value-text}", "name": "{i18n:scada.symbol.value-text}",
"type": "color", "type": "color",
"default": "#0000008A", "default": "#0000008A",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "transparent", "id": "transparent",
"name": "{i18n:scada.symbol.transparent-mode}", "name": "{i18n:scada.symbol.transparent-mode}",
"type": "switch", "type": "switch",
"default": false, "default": false,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "scale", "id": "scale",
"name": "{i18n:scada.symbol.scale}", "name": "{i18n:scada.symbol.scale}",
"type": "switch", "type": "switch",
"default": false, "default": false,
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "transparent", "disableOnProperty": "transparent",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null, },
"max": null, {
"step": null "id": "scaleDisplayFormat",
"name": "{i18n:scada.symbol.scale}",
"type": "select",
"default": true,
"subLabel": "{i18n:scada.symbol.display-format}",
"disableOnProperty": "scale",
"items": [
{
"value": true,
"label": "Percentage"
},
{
"value": false,
"label": "Absolute"
}
],
"disabled": false,
"visible": true
}, },
{ {
"id": "majorIntervals", "id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}", "name": "{i18n:scada.symbol.major-ticks}",
"type": "number", "type": "number",
"default": 10, "default": 10,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "step": 1,
"step": 1 "disabled": false,
"visible": true
},
{
"id": "majorUnits",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "units",
"subLabel": "{i18n:scada.symbol.units}",
"divider": true,
"disableOnProperty": "scale",
"disabled": false,
"visible": true
}, },
{ {
"id": "majorFont", "id": "majorFont",
@ -446,128 +407,84 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorColor", "id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#00000061", "default": "#00000061",
"required": null,
"subLabel": "{i18n:scada.symbol.normal}", "subLabel": "{i18n:scada.symbol.normal}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorWarningColor", "id": "majorWarningColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorCriticalColor", "id": "majorCriticalColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorIntervals", "id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}", "name": "{i18n:scada.symbol.minor-ticks}",
"type": "number", "type": "number",
"default": 5, "default": 5,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "disabled": false,
"step": null "visible": true
}, },
{ {
"id": "minorColor", "id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#0000001F", "default": "#0000001F",
"required": null,
"subLabel": "{i18n:scada.symbol.normal}", "subLabel": "{i18n:scada.symbol.normal}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorWarningColor", "id": "minorWarningColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorCriticalColor", "id": "minorCriticalColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
} }
] ]
}]]></tb:metadata> }]]></tb:metadata>

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 137 KiB

140
application/src/main/data/json/system/scada_symbols/horizontal-tank-hp.svg

@ -38,7 +38,7 @@
}, },
{ {
"tag": "scale", "tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 3;\n var majorIntervalLength = 592 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(208, y, 240, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n if (i === 0) {\n majorTickText.attr({x: 198, y: y + 10, 'text-anchor': 'end', class: 'majorTickText'});\n } else if (i === majorIntervals) {\n majorTickText.attr({x: 198, y: y - 5, 'text-anchor': 'end', class: 'majorTickText'});\n } else {\n majorTickText.attr({x: 198, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n }\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(220, minorY, 240, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 3;\n var majorIntervalLength = 592 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n var tankCapacity = ctx.properties.scaleDisplayFormat ? 100 : (ctx.values.tankCapacity || 100);\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(208, y, 240, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = ctx.api.formatValue((tankCapacity - i * (tankCapacity/majorIntervals)).toFixed(0), 0, ctx.properties.majorUnits, false);\n var majorTickText = ctx.svg.text(majorText);\n if (i === 0) {\n majorTickText.attr({x: 198, y: y + 10, 'text-anchor': 'end', class: 'majorTickText'});\n } else if (i === majorIntervals) {\n majorTickText.attr({x: 198, y: y - 5, 'text-anchor': 'end', class: 'majorTickText'});\n } else {\n majorTickText.attr({x: 198, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n }\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(220, minorY, 240, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null "actions": null
}, },
{ {
@ -307,64 +307,67 @@
"name": "{i18n:scada.symbol.tank-color}", "name": "{i18n:scada.symbol.tank-color}",
"type": "color", "type": "color",
"default": "#EBEBEB", "default": "#EBEBEB",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "fluidColor", "id": "fluidColor",
"name": "{i18n:scada.symbol.fluid-color}", "name": "{i18n:scada.symbol.fluid-color}",
"type": "color", "type": "color",
"default": "#C8DFF7", "default": "#C8DFF7",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "scale", "id": "scale",
"name": "{i18n:scada.symbol.scale}", "name": "{i18n:scada.symbol.scale}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null, },
"fieldSuffix": null, {
"disableOnProperty": null, "id": "scaleDisplayFormat",
"rowClass": "", "name": "{i18n:scada.symbol.scale}",
"fieldClass": "", "type": "select",
"min": null, "default": true,
"max": null, "subLabel": "{i18n:scada.symbol.display-format}",
"step": null "disableOnProperty": "scale",
"items": [
{
"value": true,
"label": "Percentage"
},
{
"value": false,
"label": "Absolute"
}
],
"disabled": false,
"visible": true
}, },
{ {
"id": "majorIntervals", "id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}", "name": "{i18n:scada.symbol.major-ticks}",
"type": "number", "type": "number",
"default": 10, "default": 10,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": false, "divider": false,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "step": 1,
"step": 1 "disabled": false,
"visible": true
},
{
"id": "majorUnits",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "units",
"subLabel": "{i18n:scada.symbol.units}",
"divider": true,
"disableOnProperty": "scale",
"disabled": false,
"visible": true
}, },
{ {
"id": "majorFont", "id": "majorFont",
@ -377,96 +380,57 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorColor", "id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks}", "name": "{i18n:scada.symbol.major-ticks}",
"type": "color", "type": "color",
"default": "#00000061", "default": "#00000061",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorIntervals", "id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}", "name": "{i18n:scada.symbol.minor-ticks}",
"type": "number", "type": "number",
"default": 5, "default": 5,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "disabled": false,
"step": null "visible": true
}, },
{ {
"id": "minorColor", "id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks}", "name": "{i18n:scada.symbol.minor-ticks}",
"type": "color", "type": "color",
"default": "#0000001F", "default": "#0000001F",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "warningColor", "id": "warningColor",
"name": "{i18n:scada.symbol.alarm-colors}", "name": "{i18n:scada.symbol.alarm-colors}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null, "disabled": false,
"disableOnProperty": null, "visible": true
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "criticalColor", "id": "criticalColor",
"name": "{i18n:scada.symbol.alarm-colors}", "name": "{i18n:scada.symbol.alarm-colors}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null, "disabled": false,
"fieldSuffix": null, "visible": true
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
} }
] ]
}]]></tb:metadata> }]]></tb:metadata>

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 49 KiB

219
application/src/main/data/json/system/scada_symbols/horizontal-tank.svg

@ -33,7 +33,7 @@
}, },
{ {
"tag": "scale", "tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 17;\n var majorIntervalLength = 568 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(715, y, 747, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 705, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(727, minorY, 747, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 17;\n var majorIntervalLength = 568 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n var tankCapacity = ctx.properties.scaleDisplayFormat ? 100 : (ctx.values.tankCapacity || 100);\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(715, y, 747, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = ctx.api.formatValue((tankCapacity - i * (tankCapacity/majorIntervals)).toFixed(0), 0, ctx.properties.majorUnits, false);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 705, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(727, minorY, 747, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null "actions": null
}, },
{ {
@ -278,80 +278,43 @@
"name": "{i18n:scada.symbol.tank-color}", "name": "{i18n:scada.symbol.tank-color}",
"type": "color", "type": "color",
"default": "#E5E5E5", "default": "#E5E5E5",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "fluidColor", "id": "fluidColor",
"name": "{i18n:scada.symbol.fluid-color}", "name": "{i18n:scada.symbol.fluid-color}",
"type": "color", "type": "color",
"default": "#1EC1F480", "default": "#1EC1F480",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueBox", "id": "valueBox",
"name": "{i18n:scada.symbol.value-box}", "name": "{i18n:scada.symbol.value-box}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueBoxColor", "id": "valueBoxColor",
"name": "{i18n:scada.symbol.value-box}", "name": "{i18n:scada.symbol.value-box}",
"type": "color", "type": "color",
"default": "#F3F3F3", "default": "#F3F3F3",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueUnits", "id": "valueUnits",
"name": "{i18n:scada.symbol.value-text}", "name": "{i18n:scada.symbol.value-text}",
"type": "units", "type": "units",
"default": "gal", "default": "gal",
"required": null,
"subLabel": "{i18n:scada.symbol.units}", "subLabel": "{i18n:scada.symbol.units}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueTextFont", "id": "valueTextFont",
@ -364,80 +327,78 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueTextColor", "id": "valueTextColor",
"name": "{i18n:scada.symbol.value-text}", "name": "{i18n:scada.symbol.value-text}",
"type": "color", "type": "color",
"default": "#0000008A", "default": "#0000008A",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "scale", "id": "scale",
"name": "{i18n:scada.symbol.scale}", "name": "{i18n:scada.symbol.scale}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null, },
"fieldSuffix": null, {
"disableOnProperty": null, "id": "scaleDisplayFormat",
"rowClass": "", "name": "{i18n:scada.symbol.scale}",
"fieldClass": "", "type": "select",
"min": null, "default": true,
"max": null, "subLabel": "{i18n:scada.symbol.display-format}",
"step": null "disableOnProperty": "scale",
"items": [
{
"value": true,
"label": "Percentage"
},
{
"value": false,
"label": "Absolute"
}
],
"disabled": false,
"visible": true
}, },
{ {
"id": "transparent", "id": "transparent",
"name": "{i18n:scada.symbol.transparent-mode}", "name": "{i18n:scada.symbol.transparent-mode}",
"type": "switch", "type": "switch",
"default": false, "default": false,
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorIntervals", "id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}", "name": "{i18n:scada.symbol.major-ticks}",
"type": "number", "type": "number",
"default": 10, "default": 10,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "step": 1,
"step": 1 "disabled": false,
"visible": true
},
{
"id": "majorUnits",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "units",
"subLabel": "{i18n:scada.symbol.units}",
"divider": true,
"disableOnProperty": "scale",
"disabled": false,
"visible": true
}, },
{ {
"id": "majorFont", "id": "majorFont",
@ -450,128 +411,84 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorColor", "id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#00000061", "default": "#00000061",
"required": null,
"subLabel": "{i18n:scada.symbol.normal}", "subLabel": "{i18n:scada.symbol.normal}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorWarningColor", "id": "majorWarningColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorCriticalColor", "id": "majorCriticalColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorIntervals", "id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}", "name": "{i18n:scada.symbol.minor-ticks}",
"type": "number", "type": "number",
"default": 5, "default": 5,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "disabled": false,
"step": null "visible": true
}, },
{ {
"id": "minorColor", "id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#0000001F", "default": "#0000001F",
"required": null,
"subLabel": "{i18n:scada.symbol.normal}", "subLabel": "{i18n:scada.symbol.normal}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorWarningColor", "id": "minorWarningColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorCriticalColor", "id": "minorCriticalColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
} }
] ]
}]]></tb:metadata> }]]></tb:metadata>

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 113 KiB

219
application/src/main/data/json/system/scada_symbols/large-cylindrical-tank.svg

@ -33,7 +33,7 @@
}, },
{ {
"tag": "scale", "tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 60;\n var majorIntervalLength = 910 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(656, y, 688, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 646, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(668, minorY, 688, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 60;\n var majorIntervalLength = 910 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n var tankCapacity = ctx.properties.scaleDisplayFormat ? 100 : (ctx.values.tankCapacity || 100);\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(656, y, 688, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = ctx.api.formatValue((tankCapacity - i * (tankCapacity/majorIntervals)).toFixed(0), 0, ctx.properties.majorUnits, false);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 646, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(668, minorY, 688, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null "actions": null
}, },
{ {
@ -278,80 +278,43 @@
"name": "{i18n:scada.symbol.tank-color}", "name": "{i18n:scada.symbol.tank-color}",
"type": "color", "type": "color",
"default": "#E5E5E5", "default": "#E5E5E5",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "fluidColor", "id": "fluidColor",
"name": "{i18n:scada.symbol.fluid-color}", "name": "{i18n:scada.symbol.fluid-color}",
"type": "color", "type": "color",
"default": "#1EC1F480", "default": "#1EC1F480",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueBox", "id": "valueBox",
"name": "{i18n:scada.symbol.value-box}", "name": "{i18n:scada.symbol.value-box}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueBoxColor", "id": "valueBoxColor",
"name": "{i18n:scada.symbol.value-box}", "name": "{i18n:scada.symbol.value-box}",
"type": "color", "type": "color",
"default": "#F3F3F3", "default": "#F3F3F3",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueUnits", "id": "valueUnits",
"name": "{i18n:scada.symbol.value-text}", "name": "{i18n:scada.symbol.value-text}",
"type": "units", "type": "units",
"default": "gal", "default": "gal",
"required": null,
"subLabel": "{i18n:scada.symbol.units}", "subLabel": "{i18n:scada.symbol.units}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueTextFont", "id": "valueTextFont",
@ -364,80 +327,78 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueTextColor", "id": "valueTextColor",
"name": "{i18n:scada.symbol.value-text}", "name": "{i18n:scada.symbol.value-text}",
"type": "color", "type": "color",
"default": "#0000008A", "default": "#0000008A",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "scale", "id": "scale",
"name": "{i18n:scada.symbol.scale}", "name": "{i18n:scada.symbol.scale}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null, },
"fieldSuffix": null, {
"disableOnProperty": null, "id": "scaleDisplayFormat",
"rowClass": "", "name": "{i18n:scada.symbol.scale}",
"fieldClass": "", "type": "select",
"min": null, "default": true,
"max": null, "subLabel": "{i18n:scada.symbol.display-format}",
"step": null "disableOnProperty": "scale",
"items": [
{
"value": true,
"label": "Percentage"
},
{
"value": false,
"label": "Absolute"
}
],
"disabled": false,
"visible": true
}, },
{ {
"id": "transparent", "id": "transparent",
"name": "{i18n:scada.symbol.transparent-mode}", "name": "{i18n:scada.symbol.transparent-mode}",
"type": "switch", "type": "switch",
"default": false, "default": false,
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorIntervals", "id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}", "name": "{i18n:scada.symbol.major-ticks}",
"type": "number", "type": "number",
"default": 10, "default": 10,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "step": 1,
"step": 1 "disabled": false,
"visible": true
},
{
"id": "majorUnits",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "units",
"subLabel": "{i18n:scada.symbol.units}",
"divider": true,
"disableOnProperty": "scale",
"disabled": false,
"visible": true
}, },
{ {
"id": "majorFont", "id": "majorFont",
@ -450,128 +411,84 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorColor", "id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#00000061", "default": "#00000061",
"required": null,
"subLabel": "{i18n:scada.symbol.normal}", "subLabel": "{i18n:scada.symbol.normal}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorWarningColor", "id": "majorWarningColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorCriticalColor", "id": "majorCriticalColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorIntervals", "id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}", "name": "{i18n:scada.symbol.minor-ticks}",
"type": "number", "type": "number",
"default": 5, "default": 5,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "disabled": false,
"step": null "visible": true
}, },
{ {
"id": "minorColor", "id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#0000001F", "default": "#0000001F",
"required": null,
"subLabel": "{i18n:scada.symbol.normal}", "subLabel": "{i18n:scada.symbol.normal}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorWarningColor", "id": "minorWarningColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorCriticalColor", "id": "minorCriticalColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
} }
] ]
}]]></tb:metadata> }]]></tb:metadata>

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 111 KiB

217
application/src/main/data/json/system/scada_symbols/large-stand-cylindrical-tank.svg

@ -34,7 +34,7 @@
}, },
{ {
"tag": "scale", "tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 60;\n var majorIntervalLength = 910 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(656, y, 688, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 646, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(668, minorY, 688, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 60;\n var majorIntervalLength = 910 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n var tankCapacity = ctx.properties.scaleDisplayFormat ? 100 : (ctx.values.tankCapacity || 100);\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(656, y, 688, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = ctx.api.formatValue((tankCapacity - i * (tankCapacity/majorIntervals)).toFixed(0), 0, ctx.properties.majorUnits, false);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 646, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(668, minorY, 688, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null "actions": null
}, },
{ {
@ -279,80 +279,43 @@
"name": "{i18n:scada.symbol.tank-color}", "name": "{i18n:scada.symbol.tank-color}",
"type": "color", "type": "color",
"default": "#E5E5E5", "default": "#E5E5E5",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "fluidColor", "id": "fluidColor",
"name": "{i18n:scada.symbol.fluid-color}", "name": "{i18n:scada.symbol.fluid-color}",
"type": "color", "type": "color",
"default": "#1EC1F480", "default": "#1EC1F480",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueBox", "id": "valueBox",
"name": "{i18n:scada.symbol.value-box}", "name": "{i18n:scada.symbol.value-box}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueBoxColor", "id": "valueBoxColor",
"name": "{i18n:scada.symbol.value-box}", "name": "{i18n:scada.symbol.value-box}",
"type": "color", "type": "color",
"default": "#F3F3F3", "default": "#F3F3F3",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueUnits", "id": "valueUnits",
"name": "{i18n:scada.symbol.value-text}", "name": "{i18n:scada.symbol.value-text}",
"type": "units", "type": "units",
"default": "gal", "default": "gal",
"required": null,
"subLabel": "{i18n:scada.symbol.units}", "subLabel": "{i18n:scada.symbol.units}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueTextFont", "id": "valueTextFont",
@ -365,80 +328,76 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueTextColor", "id": "valueTextColor",
"name": "{i18n:scada.symbol.value-text}", "name": "{i18n:scada.symbol.value-text}",
"type": "color", "type": "color",
"default": "#0000008A", "default": "#0000008A",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "scale", "id": "scale",
"name": "{i18n:scada.symbol.scale}", "name": "{i18n:scada.symbol.scale}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null, },
"fieldSuffix": null, {
"disableOnProperty": null, "id": "scaleDisplayFormat",
"rowClass": "", "name": "{i18n:scada.symbol.scale}",
"fieldClass": "", "type": "select",
"min": null, "default": true,
"max": null, "subLabel": "{i18n:scada.symbol.display-format}",
"step": null "disableOnProperty": "scale",
"items": [
{
"value": true,
"label": "Percentage"
},
{
"value": false,
"label": "Absolute"
}
]
}, },
{ {
"id": "transparent", "id": "transparent",
"name": "{i18n:scada.symbol.transparent-mode}", "name": "{i18n:scada.symbol.transparent-mode}",
"type": "switch", "type": "switch",
"default": false, "default": false,
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorIntervals", "id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}", "name": "{i18n:scada.symbol.major-ticks}",
"type": "number", "type": "number",
"default": 10, "default": 10,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "step": 1,
"step": 1 "disabled": false,
"visible": true
},
{
"id": "majorUnits",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "units",
"subLabel": "{i18n:scada.symbol.units}",
"divider": true,
"disableOnProperty": "scale",
"disabled": false,
"visible": true
}, },
{ {
"id": "majorFont", "id": "majorFont",
@ -451,128 +410,84 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorColor", "id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#00000061", "default": "#00000061",
"required": null,
"subLabel": "{i18n:scada.symbol.normal}", "subLabel": "{i18n:scada.symbol.normal}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorWarningColor", "id": "majorWarningColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorCriticalColor", "id": "majorCriticalColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorIntervals", "id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}", "name": "{i18n:scada.symbol.minor-ticks}",
"type": "number", "type": "number",
"default": 5, "default": 5,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "disabled": false,
"step": null "visible": true
}, },
{ {
"id": "minorColor", "id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#0000001F", "default": "#0000001F",
"required": null,
"subLabel": "{i18n:scada.symbol.normal}", "subLabel": "{i18n:scada.symbol.normal}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorWarningColor", "id": "minorWarningColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorCriticalColor", "id": "minorCriticalColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
} }
] ]
}]]></tb:metadata> }]]></tb:metadata>

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 118 KiB

219
application/src/main/data/json/system/scada_symbols/large-stand-vertical-tank.svg

@ -34,7 +34,7 @@
}, },
{ {
"tag": "scale", "tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 203;\n var majorIntervalLength = 763 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(676, y, 708, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 666, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(688, minorY, 708, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 203;\n var majorIntervalLength = 763 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n var tankCapacity = ctx.properties.scaleDisplayFormat ? 100 : (ctx.values.tankCapacity || 100);\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(676, y, 708, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = ctx.api.formatValue((tankCapacity - i * (tankCapacity/majorIntervals)).toFixed(0), 0, ctx.properties.majorUnits, false);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 666, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(688, minorY, 708, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null "actions": null
}, },
{ {
@ -279,80 +279,43 @@
"name": "{i18n:scada.symbol.tank-color}", "name": "{i18n:scada.symbol.tank-color}",
"type": "color", "type": "color",
"default": "#E5E5E5", "default": "#E5E5E5",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "fluidColor", "id": "fluidColor",
"name": "{i18n:scada.symbol.fluid-color}", "name": "{i18n:scada.symbol.fluid-color}",
"type": "color", "type": "color",
"default": "#1EC1F480", "default": "#1EC1F480",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueBox", "id": "valueBox",
"name": "{i18n:scada.symbol.value-box}", "name": "{i18n:scada.symbol.value-box}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueBoxColor", "id": "valueBoxColor",
"name": "{i18n:scada.symbol.value-box}", "name": "{i18n:scada.symbol.value-box}",
"type": "color", "type": "color",
"default": "#F3F3F3", "default": "#F3F3F3",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueUnits", "id": "valueUnits",
"name": "{i18n:scada.symbol.value-text}", "name": "{i18n:scada.symbol.value-text}",
"type": "units", "type": "units",
"default": "gal", "default": "gal",
"required": null,
"subLabel": "{i18n:scada.symbol.units}", "subLabel": "{i18n:scada.symbol.units}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueTextFont", "id": "valueTextFont",
@ -365,80 +328,78 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueTextColor", "id": "valueTextColor",
"name": "{i18n:scada.symbol.value-text}", "name": "{i18n:scada.symbol.value-text}",
"type": "color", "type": "color",
"default": "#0000008A", "default": "#0000008A",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "scale", "id": "scale",
"name": "{i18n:scada.symbol.scale}", "name": "{i18n:scada.symbol.scale}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null, },
"fieldSuffix": null, {
"disableOnProperty": null, "id": "scaleDisplayFormat",
"rowClass": "", "name": "{i18n:scada.symbol.scale}",
"fieldClass": "", "type": "select",
"min": null, "default": true,
"max": null, "subLabel": "{i18n:scada.symbol.display-format}",
"step": null "disableOnProperty": "scale",
"items": [
{
"value": true,
"label": "Percentage"
},
{
"value": false,
"label": "Absolute"
}
],
"disabled": false,
"visible": true
}, },
{ {
"id": "transparent", "id": "transparent",
"name": "{i18n:scada.symbol.transparent-mode}", "name": "{i18n:scada.symbol.transparent-mode}",
"type": "switch", "type": "switch",
"default": false, "default": false,
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorIntervals", "id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}", "name": "{i18n:scada.symbol.major-ticks}",
"type": "number", "type": "number",
"default": 10, "default": 10,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "step": 1,
"step": 1 "disabled": false,
"visible": true
},
{
"id": "majorUnits",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "units",
"subLabel": "{i18n:scada.symbol.units}",
"divider": true,
"disableOnProperty": "scale",
"disabled": false,
"visible": true
}, },
{ {
"id": "majorFont", "id": "majorFont",
@ -451,128 +412,84 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorColor", "id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#00000061", "default": "#00000061",
"required": null,
"subLabel": "{i18n:scada.symbol.normal}", "subLabel": "{i18n:scada.symbol.normal}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorWarningColor", "id": "majorWarningColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorCriticalColor", "id": "majorCriticalColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorIntervals", "id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}", "name": "{i18n:scada.symbol.minor-ticks}",
"type": "number", "type": "number",
"default": 5, "default": 5,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "disabled": false,
"step": null "visible": true
}, },
{ {
"id": "minorColor", "id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#0000001F", "default": "#0000001F",
"required": null,
"subLabel": "{i18n:scada.symbol.normal}", "subLabel": "{i18n:scada.symbol.normal}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorWarningColor", "id": "minorWarningColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorCriticalColor", "id": "minorCriticalColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
} }
] ]
}]]></tb:metadata> }]]></tb:metadata>

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 119 KiB

219
application/src/main/data/json/system/scada_symbols/large-vertical-tank.svg

@ -33,7 +33,7 @@
}, },
{ {
"tag": "scale", "tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 203;\n var majorIntervalLength = 763 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(676, y, 708, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 666, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(688, minorY, 708, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 203;\n var majorIntervalLength = 763 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n var tankCapacity = ctx.properties.scaleDisplayFormat ? 100 : (ctx.values.tankCapacity || 100);\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(676, y, 708, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = ctx.api.formatValue((tankCapacity - i * (tankCapacity/majorIntervals)).toFixed(0), 0, ctx.properties.majorUnits, false);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 666, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(688, minorY, 708, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null "actions": null
}, },
{ {
@ -278,80 +278,43 @@
"name": "{i18n:scada.symbol.tank-color}", "name": "{i18n:scada.symbol.tank-color}",
"type": "color", "type": "color",
"default": "#E5E5E5", "default": "#E5E5E5",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "fluidColor", "id": "fluidColor",
"name": "{i18n:scada.symbol.fluid-color}", "name": "{i18n:scada.symbol.fluid-color}",
"type": "color", "type": "color",
"default": "#1EC1F480", "default": "#1EC1F480",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueBox", "id": "valueBox",
"name": "{i18n:scada.symbol.value-box}", "name": "{i18n:scada.symbol.value-box}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueBoxColor", "id": "valueBoxColor",
"name": "{i18n:scada.symbol.value-box}", "name": "{i18n:scada.symbol.value-box}",
"type": "color", "type": "color",
"default": "#F3F3F3", "default": "#F3F3F3",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueUnits", "id": "valueUnits",
"name": "{i18n:scada.symbol.value-text}", "name": "{i18n:scada.symbol.value-text}",
"type": "units", "type": "units",
"default": "gal", "default": "gal",
"required": null,
"subLabel": "{i18n:scada.symbol.units}", "subLabel": "{i18n:scada.symbol.units}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueTextFont", "id": "valueTextFont",
@ -364,80 +327,78 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueTextColor", "id": "valueTextColor",
"name": "{i18n:scada.symbol.value-text}", "name": "{i18n:scada.symbol.value-text}",
"type": "color", "type": "color",
"default": "#0000008A", "default": "#0000008A",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "scale", "id": "scale",
"name": "{i18n:scada.symbol.scale}", "name": "{i18n:scada.symbol.scale}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null, },
"fieldSuffix": null, {
"disableOnProperty": null, "id": "scaleDisplayFormat",
"rowClass": "", "name": "{i18n:scada.symbol.scale}",
"fieldClass": "", "type": "select",
"min": null, "default": true,
"max": null, "subLabel": "{i18n:scada.symbol.display-format}",
"step": null "disableOnProperty": "scale",
"items": [
{
"value": true,
"label": "Percentage"
},
{
"value": false,
"label": "Absolute"
}
],
"disabled": false,
"visible": true
}, },
{ {
"id": "transparent", "id": "transparent",
"name": "{i18n:scada.symbol.transparent-mode}", "name": "{i18n:scada.symbol.transparent-mode}",
"type": "switch", "type": "switch",
"default": false, "default": false,
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorIntervals", "id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}", "name": "{i18n:scada.symbol.major-ticks}",
"type": "number", "type": "number",
"default": 10, "default": 10,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "step": 1,
"step": 1 "disabled": false,
"visible": true
},
{
"id": "majorUnits",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "units",
"subLabel": "{i18n:scada.symbol.units}",
"divider": true,
"disableOnProperty": "scale",
"disabled": false,
"visible": true
}, },
{ {
"id": "majorFont", "id": "majorFont",
@ -450,128 +411,84 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorColor", "id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#00000061", "default": "#00000061",
"required": null,
"subLabel": "{i18n:scada.symbol.normal}", "subLabel": "{i18n:scada.symbol.normal}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorWarningColor", "id": "majorWarningColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorCriticalColor", "id": "majorCriticalColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorIntervals", "id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}", "name": "{i18n:scada.symbol.minor-ticks}",
"type": "number", "type": "number",
"default": 5, "default": 5,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "disabled": false,
"step": null "visible": true
}, },
{ {
"id": "minorColor", "id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#0000001F", "default": "#0000001F",
"required": null,
"subLabel": "{i18n:scada.symbol.normal}", "subLabel": "{i18n:scada.symbol.normal}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorWarningColor", "id": "minorWarningColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorCriticalColor", "id": "minorCriticalColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
} }
] ]
}]]></tb:metadata> }]]></tb:metadata>

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 112 KiB

4
application/src/main/data/json/system/scada_symbols/long-bottom-filter.svg

@ -1,7 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="600" fill="none" version="1.1" viewBox="0 0 200 600"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="600" fill="none" version="1.1" viewBox="0 0 200 600">
<tb:metadata xmlns=""><![CDATA[{ <tb:metadata xmlns=""><![CDATA[{
"title": "Long bottom filter with configurable click actions for custom operations, dashboard manipulation, etc.", "title": "Long bottom filter",
"description": "Long bottom filter", "description": "Long bottom filter with configurable click actions for custom operations, dashboard manipulation, etc.",
"searchTags": [ "searchTags": [
"filter" "filter"
], ],

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

4
application/src/main/data/json/system/scada_symbols/long-top-filter.svg

@ -1,7 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="600" fill="none" version="1.1" viewBox="0 0 200 600"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="600" fill="none" version="1.1" viewBox="0 0 200 600">
<tb:metadata xmlns=""><![CDATA[{ <tb:metadata xmlns=""><![CDATA[{
"title": "Long top filter with configurable click actions for custom operations, dashboard manipulation, etc.", "title": "Long top filter",
"description": "Title\nLong top filter\n", "description": "Long top filter with configurable click actions for custom operations, dashboard manipulation, etc.",
"searchTags": [ "searchTags": [
"filter" "filter"
], ],

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

140
application/src/main/data/json/system/scada_symbols/pool-hp.svg

@ -38,7 +38,7 @@
}, },
{ {
"tag": "scale", "tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 3;\n var majorIntervalLength = 792 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(298, y, 330, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n if (i === 0) {\n majorTickText.attr({x: 288, y: y + 10, 'text-anchor': 'end', class: 'majorTickText'});\n } else if (i === majorIntervals) {\n majorTickText.attr({x: 288, y: y - 5, 'text-anchor': 'end', class: 'majorTickText'});\n } else {\n majorTickText.attr({x: 288, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n }\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(310, minorY, 330, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 3;\n var majorIntervalLength = 792 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n var tankCapacity = ctx.properties.scaleDisplayFormat ? 100 : (ctx.values.tankCapacity || 100);\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(298, y, 330, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = ctx.api.formatValue((tankCapacity - i * (tankCapacity/majorIntervals)).toFixed(0), 0, ctx.properties.majorUnits, false);\n var majorTickText = ctx.svg.text(majorText);\n if (i === 0) {\n majorTickText.attr({x: 288, y: y + 10, 'text-anchor': 'end', class: 'majorTickText'});\n } else if (i === majorIntervals) {\n majorTickText.attr({x: 288, y: y - 5, 'text-anchor': 'end', class: 'majorTickText'});\n } else {\n majorTickText.attr({x: 288, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n }\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(310, minorY, 330, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null "actions": null
}, },
{ {
@ -307,64 +307,57 @@
"name": "{i18n:scada.symbol.tank-color}", "name": "{i18n:scada.symbol.tank-color}",
"type": "color", "type": "color",
"default": "#EBEBEB", "default": "#EBEBEB",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "fluidColor", "id": "fluidColor",
"name": "{i18n:scada.symbol.fluid-color}", "name": "{i18n:scada.symbol.fluid-color}",
"type": "color", "type": "color",
"default": "#C8DFF7", "default": "#C8DFF7",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "scale", "id": "scale",
"name": "{i18n:scada.symbol.scale}", "name": "{i18n:scada.symbol.scale}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null, },
"fieldSuffix": null, {
"disableOnProperty": null, "id": "scaleDisplayFormat",
"rowClass": "", "name": "{i18n:scada.symbol.scale}",
"fieldClass": "", "type": "select",
"min": null, "default": true,
"max": null, "subLabel": "{i18n:scada.symbol.display-format}",
"step": null "disableOnProperty": "scale",
"items": [
{
"value": true,
"label": "Percentage"
},
{
"value": false,
"label": "Absolute"
}
],
"disabled": false,
"visible": true
}, },
{ {
"id": "majorIntervals", "id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}", "name": "{i18n:scada.symbol.major-ticks}",
"type": "number", "type": "number",
"default": 10, "default": 10,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": false, "divider": false,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "step": 1,
"step": 1 "disabled": false,
"visible": true
}, },
{ {
"id": "majorFont", "id": "majorFont",
@ -377,96 +370,67 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorColor", "id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks}", "name": "{i18n:scada.symbol.major-ticks}",
"type": "color", "type": "color",
"default": "#00000061", "default": "#00000061",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorIntervals", "id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}", "name": "{i18n:scada.symbol.minor-ticks}",
"type": "number", "type": "number",
"default": 5, "default": 5,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "disabled": false,
"step": null "visible": true
},
{
"id": "majorUnits",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "units",
"subLabel": "{i18n:scada.symbol.units}",
"divider": true,
"disableOnProperty": "scale",
"disabled": false,
"visible": true
}, },
{ {
"id": "minorColor", "id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks}", "name": "{i18n:scada.symbol.minor-ticks}",
"type": "color", "type": "color",
"default": "#0000001F", "default": "#0000001F",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "warningColor", "id": "warningColor",
"name": "{i18n:scada.symbol.alarm-colors}", "name": "{i18n:scada.symbol.alarm-colors}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null, "disabled": false,
"disableOnProperty": null, "visible": true
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "criticalColor", "id": "criticalColor",
"name": "{i18n:scada.symbol.alarm-colors}", "name": "{i18n:scada.symbol.alarm-colors}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null, "disabled": false,
"fieldSuffix": null, "visible": true
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
} }
] ]
}]]></tb:metadata> }]]></tb:metadata>

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

4
application/src/main/data/json/system/scada_symbols/short-top-filter.svg

@ -1,7 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="400" fill="none" version="1.1" viewBox="0 0 200 400"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="400" fill="none" version="1.1" viewBox="0 0 200 400">
<tb:metadata xmlns=""><![CDATA[{ <tb:metadata xmlns=""><![CDATA[{
"title": "Short top filter with configurable click actions for custom operations, dashboard manipulation, etc.", "title": "Short top filter",
"description": "Short top filter", "description": "Short top filter with configurable click actions for custom operations, dashboard manipulation, etc.",
"searchTags": [ "searchTags": [
"filter" "filter"
], ],

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

140
application/src/main/data/json/system/scada_symbols/short-vertical-tank-hp.svg

@ -38,7 +38,7 @@
}, },
{ {
"tag": "scale", "tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 3;\n var majorIntervalLength = 594 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(170, y, 202, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n if (i === 0) {\n majorTickText.attr({x: 160, y: y + 10, 'text-anchor': 'end', class: 'majorTickText'});\n } else if (i === majorIntervals) {\n majorTickText.attr({x: 160, y: y - 5, 'text-anchor': 'end', class: 'majorTickText'});\n } else {\n majorTickText.attr({x: 160, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n }\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(182, minorY, 202, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 3;\n var majorIntervalLength = 594 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n var tankCapacity = ctx.properties.scaleDisplayFormat ? 100 : (ctx.values.tankCapacity || 100);\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(170, y, 202, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = ctx.api.formatValue((tankCapacity - i * (tankCapacity/majorIntervals)).toFixed(0), 0, ctx.properties.majorUnits, false);\n var majorTickText = ctx.svg.text(majorText);\n if (i === 0) {\n majorTickText.attr({x: 160, y: y + 10, 'text-anchor': 'end', class: 'majorTickText'});\n } else if (i === majorIntervals) {\n majorTickText.attr({x: 160, y: y - 5, 'text-anchor': 'end', class: 'majorTickText'});\n } else {\n majorTickText.attr({x: 160, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n }\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(182, minorY, 202, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null "actions": null
}, },
{ {
@ -307,64 +307,67 @@
"name": "{i18n:scada.symbol.tank-color}", "name": "{i18n:scada.symbol.tank-color}",
"type": "color", "type": "color",
"default": "#EBEBEB", "default": "#EBEBEB",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "fluidColor", "id": "fluidColor",
"name": "{i18n:scada.symbol.fluid-color}", "name": "{i18n:scada.symbol.fluid-color}",
"type": "color", "type": "color",
"default": "#C8DFF7", "default": "#C8DFF7",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "scale", "id": "scale",
"name": "{i18n:scada.symbol.scale}", "name": "{i18n:scada.symbol.scale}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null, },
"fieldSuffix": null, {
"disableOnProperty": null, "id": "scaleDisplayFormat",
"rowClass": "", "name": "{i18n:scada.symbol.scale}",
"fieldClass": "", "type": "select",
"min": null, "default": true,
"max": null, "subLabel": "{i18n:scada.symbol.display-format}",
"step": null "disableOnProperty": "scale",
"items": [
{
"value": true,
"label": "Percentage"
},
{
"value": false,
"label": "Absolute"
}
],
"disabled": false,
"visible": true
}, },
{ {
"id": "majorIntervals", "id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}", "name": "{i18n:scada.symbol.major-ticks}",
"type": "number", "type": "number",
"default": 10, "default": 10,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": false, "divider": false,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "step": 1,
"step": 1 "disabled": false,
"visible": true
},
{
"id": "majorUnits",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "units",
"subLabel": "{i18n:scada.symbol.units}",
"divider": true,
"disableOnProperty": "scale",
"disabled": false,
"visible": true
}, },
{ {
"id": "majorFont", "id": "majorFont",
@ -377,96 +380,57 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorColor", "id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks}", "name": "{i18n:scada.symbol.major-ticks}",
"type": "color", "type": "color",
"default": "#00000061", "default": "#00000061",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorIntervals", "id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}", "name": "{i18n:scada.symbol.minor-ticks}",
"type": "number", "type": "number",
"default": 5, "default": 5,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "disabled": false,
"step": null "visible": true
}, },
{ {
"id": "minorColor", "id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks}", "name": "{i18n:scada.symbol.minor-ticks}",
"type": "color", "type": "color",
"default": "#0000001F", "default": "#0000001F",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "warningColor", "id": "warningColor",
"name": "{i18n:scada.symbol.alarm-colors}", "name": "{i18n:scada.symbol.alarm-colors}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null, "disabled": false,
"disableOnProperty": null, "visible": true
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "criticalColor", "id": "criticalColor",
"name": "{i18n:scada.symbol.alarm-colors}", "name": "{i18n:scada.symbol.alarm-colors}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null, "disabled": false,
"fieldSuffix": null, "visible": true
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
} }
] ]
}]]></tb:metadata> }]]></tb:metadata>

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 50 KiB

219
application/src/main/data/json/system/scada_symbols/small-cylindrical-tank.svg

@ -34,7 +34,7 @@
}, },
{ {
"tag": "scale", "tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 45;\n var majorIntervalLength = 525 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(340, y, 372, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 330, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(352, minorY, 372, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 45;\n var majorIntervalLength = 525 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n var tankCapacity = ctx.properties.scaleDisplayFormat ? 100 : (ctx.values.tankCapacity || 100);\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(340, y, 372, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = ctx.api.formatValue((tankCapacity - i * (tankCapacity/majorIntervals)).toFixed(0), 0, ctx.properties.majorUnits, false);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 330, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(352, minorY, 372, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null "actions": null
}, },
{ {
@ -279,80 +279,43 @@
"name": "{i18n:scada.symbol.tank-color}", "name": "{i18n:scada.symbol.tank-color}",
"type": "color", "type": "color",
"default": "#E5E5E5", "default": "#E5E5E5",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "fluidColor", "id": "fluidColor",
"name": "{i18n:scada.symbol.fluid-color}", "name": "{i18n:scada.symbol.fluid-color}",
"type": "color", "type": "color",
"default": "#1EC1F480", "default": "#1EC1F480",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueBox", "id": "valueBox",
"name": "{i18n:scada.symbol.value-box}", "name": "{i18n:scada.symbol.value-box}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueBoxColor", "id": "valueBoxColor",
"name": "{i18n:scada.symbol.value-box}", "name": "{i18n:scada.symbol.value-box}",
"type": "color", "type": "color",
"default": "#F3F3F3", "default": "#F3F3F3",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": true,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueUnits", "id": "valueUnits",
"name": "{i18n:scada.symbol.value-text}", "name": "{i18n:scada.symbol.value-text}",
"type": "units", "type": "units",
"default": "gal", "default": "gal",
"required": null,
"subLabel": "{i18n:scada.symbol.units}", "subLabel": "{i18n:scada.symbol.units}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": true,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueTextFont", "id": "valueTextFont",
@ -365,80 +328,78 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": true,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueTextColor", "id": "valueTextColor",
"name": "{i18n:scada.symbol.value-text}", "name": "{i18n:scada.symbol.value-text}",
"type": "color", "type": "color",
"default": "#0000008A", "default": "#0000008A",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": true,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "scale", "id": "scale",
"name": "{i18n:scada.symbol.scale}", "name": "{i18n:scada.symbol.scale}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null, },
"fieldSuffix": null, {
"disableOnProperty": null, "id": "scaleDisplayFormat",
"rowClass": "", "name": "{i18n:scada.symbol.scale}",
"fieldClass": "", "type": "select",
"min": null, "default": true,
"max": null, "subLabel": "{i18n:scada.symbol.display-format}",
"step": null "disableOnProperty": "scale",
"items": [
{
"value": true,
"label": "Percentage"
},
{
"value": false,
"label": "Absolute"
}
],
"disabled": false,
"visible": true
}, },
{ {
"id": "transparent", "id": "transparent",
"name": "{i18n:scada.symbol.transparent-mode}", "name": "{i18n:scada.symbol.transparent-mode}",
"type": "switch", "type": "switch",
"default": false, "default": false,
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorIntervals", "id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}", "name": "{i18n:scada.symbol.major-ticks}",
"type": "number", "type": "number",
"default": 5, "default": 5,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "step": 1,
"step": 1 "disabled": false,
"visible": true
},
{
"id": "majorUnits",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "units",
"subLabel": "{i18n:scada.symbol.units}",
"divider": true,
"disableOnProperty": "scale",
"disabled": false,
"visible": true
}, },
{ {
"id": "majorFont", "id": "majorFont",
@ -451,128 +412,84 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorColor", "id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#00000061", "default": "#00000061",
"required": null,
"subLabel": "{i18n:scada.symbol.normal}", "subLabel": "{i18n:scada.symbol.normal}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorWarningColor", "id": "majorWarningColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorCriticalColor", "id": "majorCriticalColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorIntervals", "id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}", "name": "{i18n:scada.symbol.minor-ticks}",
"type": "number", "type": "number",
"default": 5, "default": 5,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "disabled": false,
"step": null "visible": true
}, },
{ {
"id": "minorColor", "id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#0000001F", "default": "#0000001F",
"required": null,
"subLabel": "{i18n:scada.symbol.normal}", "subLabel": "{i18n:scada.symbol.normal}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorWarningColor", "id": "minorWarningColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorCriticalColor", "id": "minorCriticalColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
} }
] ]
}]]></tb:metadata> }]]></tb:metadata>

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 99 KiB

219
application/src/main/data/json/system/scada_symbols/small-spherical-tank.svg

@ -34,7 +34,7 @@
}, },
{ {
"tag": "scale", "tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 23;\n var majorIntervalLength = 560 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(268, y, 300, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 258, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(280, minorY, 300, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 23;\n var majorIntervalLength = 560 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n var tankCapacity = ctx.properties.scaleDisplayFormat ? 100 : (ctx.values.tankCapacity || 100);\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(268, y, 300, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = ctx.api.formatValue((tankCapacity - i * (tankCapacity/majorIntervals)).toFixed(0), 0, ctx.properties.majorUnits, false);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 258, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(280, minorY, 300, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null "actions": null
}, },
{ {
@ -279,80 +279,43 @@
"name": "{i18n:scada.symbol.tank-color}", "name": "{i18n:scada.symbol.tank-color}",
"type": "color", "type": "color",
"default": "#E5E5E5", "default": "#E5E5E5",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "fluidColor", "id": "fluidColor",
"name": "{i18n:scada.symbol.fluid-color}", "name": "{i18n:scada.symbol.fluid-color}",
"type": "color", "type": "color",
"default": "#1EC1F480", "default": "#1EC1F480",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueBox", "id": "valueBox",
"name": "{i18n:scada.symbol.value-box}", "name": "{i18n:scada.symbol.value-box}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueBoxColor", "id": "valueBoxColor",
"name": "{i18n:scada.symbol.value-box}", "name": "{i18n:scada.symbol.value-box}",
"type": "color", "type": "color",
"default": "#F3F3F3", "default": "#F3F3F3",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": true,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueUnits", "id": "valueUnits",
"name": "{i18n:scada.symbol.value-text}", "name": "{i18n:scada.symbol.value-text}",
"type": "units", "type": "units",
"default": "gal", "default": "gal",
"required": null,
"subLabel": "{i18n:scada.symbol.units}", "subLabel": "{i18n:scada.symbol.units}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": true,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueTextFont", "id": "valueTextFont",
@ -365,80 +328,78 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": true,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueTextColor", "id": "valueTextColor",
"name": "{i18n:scada.symbol.value-text}", "name": "{i18n:scada.symbol.value-text}",
"type": "color", "type": "color",
"default": "#0000008A", "default": "#0000008A",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": true,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "scale", "id": "scale",
"name": "{i18n:scada.symbol.scale}", "name": "{i18n:scada.symbol.scale}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null, },
"fieldSuffix": null, {
"disableOnProperty": null, "id": "scaleDisplayFormat",
"rowClass": "", "name": "{i18n:scada.symbol.scale}",
"fieldClass": "", "type": "select",
"min": null, "default": true,
"max": null, "subLabel": "{i18n:scada.symbol.display-format}",
"step": null "disableOnProperty": "scale",
"items": [
{
"value": true,
"label": "Percentage"
},
{
"value": false,
"label": "Absolute"
}
],
"disabled": false,
"visible": true
}, },
{ {
"id": "transparent", "id": "transparent",
"name": "{i18n:scada.symbol.transparent-mode}", "name": "{i18n:scada.symbol.transparent-mode}",
"type": "switch", "type": "switch",
"default": false, "default": false,
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorIntervals", "id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}", "name": "{i18n:scada.symbol.major-ticks}",
"type": "number", "type": "number",
"default": 5, "default": 5,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "step": 1,
"step": 1 "disabled": false,
"visible": true
},
{
"id": "majorUnits",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "units",
"subLabel": "{i18n:scada.symbol.units}",
"divider": true,
"disableOnProperty": "scale",
"disabled": false,
"visible": true
}, },
{ {
"id": "majorFont", "id": "majorFont",
@ -451,128 +412,84 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorColor", "id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#00000061", "default": "#00000061",
"required": null,
"subLabel": "{i18n:scada.symbol.normal}", "subLabel": "{i18n:scada.symbol.normal}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorWarningColor", "id": "majorWarningColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorCriticalColor", "id": "majorCriticalColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorIntervals", "id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}", "name": "{i18n:scada.symbol.minor-ticks}",
"type": "number", "type": "number",
"default": 5, "default": 5,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "disabled": false,
"step": null "visible": true
}, },
{ {
"id": "minorColor", "id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#0000001F", "default": "#0000001F",
"required": null,
"subLabel": "{i18n:scada.symbol.normal}", "subLabel": "{i18n:scada.symbol.normal}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorWarningColor", "id": "minorWarningColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorCriticalColor", "id": "minorCriticalColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
} }
] ]
}]]></tb:metadata> }]]></tb:metadata>

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 104 KiB

219
application/src/main/data/json/system/scada_symbols/spherical-tank.svg

@ -34,7 +34,7 @@
}, },
{ {
"tag": "scale", "tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 23;\n var majorIntervalLength = 960 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(458, y, 490, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 448, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(470, minorY, 490, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 23;\n var majorIntervalLength = 960 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n var tankCapacity = ctx.properties.scaleDisplayFormat ? 100 : (ctx.values.tankCapacity || 100);\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(458, y, 490, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = ctx.api.formatValue((tankCapacity - i * (tankCapacity/majorIntervals)).toFixed(0), 0, ctx.properties.majorUnits, false);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 448, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(470, minorY, 490, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null "actions": null
}, },
{ {
@ -279,80 +279,43 @@
"name": "{i18n:scada.symbol.tank-color}", "name": "{i18n:scada.symbol.tank-color}",
"type": "color", "type": "color",
"default": "#E5E5E5", "default": "#E5E5E5",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "fluidColor", "id": "fluidColor",
"name": "{i18n:scada.symbol.fluid-color}", "name": "{i18n:scada.symbol.fluid-color}",
"type": "color", "type": "color",
"default": "#1EC1F480", "default": "#1EC1F480",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueBox", "id": "valueBox",
"name": "{i18n:scada.symbol.value-box}", "name": "{i18n:scada.symbol.value-box}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueBoxColor", "id": "valueBoxColor",
"name": "{i18n:scada.symbol.value-box}", "name": "{i18n:scada.symbol.value-box}",
"type": "color", "type": "color",
"default": "#F3F3F3", "default": "#F3F3F3",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": true,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueUnits", "id": "valueUnits",
"name": "{i18n:scada.symbol.value-text}", "name": "{i18n:scada.symbol.value-text}",
"type": "units", "type": "units",
"default": "gal", "default": "gal",
"required": null,
"subLabel": "{i18n:scada.symbol.units}", "subLabel": "{i18n:scada.symbol.units}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": true,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueTextFont", "id": "valueTextFont",
@ -365,80 +328,78 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": true,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueTextColor", "id": "valueTextColor",
"name": "{i18n:scada.symbol.value-text}", "name": "{i18n:scada.symbol.value-text}",
"type": "color", "type": "color",
"default": "#0000008A", "default": "#0000008A",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": true,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "scale", "id": "scale",
"name": "{i18n:scada.symbol.scale}", "name": "{i18n:scada.symbol.scale}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null, },
"fieldSuffix": null, {
"disableOnProperty": null, "id": "scaleDisplayFormat",
"rowClass": "", "name": "{i18n:scada.symbol.scale}",
"fieldClass": "", "type": "select",
"min": null, "default": true,
"max": null, "subLabel": "{i18n:scada.symbol.display-format}",
"step": null "disableOnProperty": "scale",
"items": [
{
"value": true,
"label": "Percentage"
},
{
"value": false,
"label": "Absolute"
}
],
"disabled": false,
"visible": true
}, },
{ {
"id": "transparent", "id": "transparent",
"name": "{i18n:scada.symbol.transparent-mode}", "name": "{i18n:scada.symbol.transparent-mode}",
"type": "switch", "type": "switch",
"default": false, "default": false,
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorIntervals", "id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}", "name": "{i18n:scada.symbol.major-ticks}",
"type": "number", "type": "number",
"default": 10, "default": 10,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "step": 1,
"step": 1 "disabled": false,
"visible": true
},
{
"id": "majorUnits",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "units",
"subLabel": "{i18n:scada.symbol.units}",
"divider": true,
"disableOnProperty": "scale",
"disabled": false,
"visible": true
}, },
{ {
"id": "majorFont", "id": "majorFont",
@ -451,128 +412,84 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorColor", "id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#00000061", "default": "#00000061",
"required": null,
"subLabel": "{i18n:scada.symbol.normal}", "subLabel": "{i18n:scada.symbol.normal}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorWarningColor", "id": "majorWarningColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorCriticalColor", "id": "majorCriticalColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorIntervals", "id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}", "name": "{i18n:scada.symbol.minor-ticks}",
"type": "number", "type": "number",
"default": 5, "default": 5,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "disabled": false,
"step": null "visible": true
}, },
{ {
"id": "minorColor", "id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#0000001F", "default": "#0000001F",
"required": null,
"subLabel": "{i18n:scada.symbol.normal}", "subLabel": "{i18n:scada.symbol.normal}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorWarningColor", "id": "minorWarningColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorCriticalColor", "id": "minorCriticalColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
} }
] ]
}]]></tb:metadata> }]]></tb:metadata>

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 117 KiB

219
application/src/main/data/json/system/scada_symbols/stand-cylindrical-tank.svg

@ -34,7 +34,7 @@
}, },
{ {
"tag": "scale", "tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 205;\n var majorIntervalLength = 760 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(340, y, 372, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 330, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(352, minorY, 372, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 205;\n var majorIntervalLength = 760 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n var tankCapacity = ctx.properties.scaleDisplayFormat ? 100 : (ctx.values.tankCapacity || 100);\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(340, y, 372, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = ctx.api.formatValue((tankCapacity - i * (tankCapacity/majorIntervals)).toFixed(0), 0, ctx.properties.majorUnits, false);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 330, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(352, minorY, 372, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null "actions": null
}, },
{ {
@ -279,80 +279,43 @@
"name": "{i18n:scada.symbol.tank-color}", "name": "{i18n:scada.symbol.tank-color}",
"type": "color", "type": "color",
"default": "#E5E5E5", "default": "#E5E5E5",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "fluidColor", "id": "fluidColor",
"name": "{i18n:scada.symbol.fluid-color}", "name": "{i18n:scada.symbol.fluid-color}",
"type": "color", "type": "color",
"default": "#1EC1F480", "default": "#1EC1F480",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueBox", "id": "valueBox",
"name": "{i18n:scada.symbol.value-box}", "name": "{i18n:scada.symbol.value-box}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueBoxColor", "id": "valueBoxColor",
"name": "{i18n:scada.symbol.value-box}", "name": "{i18n:scada.symbol.value-box}",
"type": "color", "type": "color",
"default": "#F3F3F3", "default": "#F3F3F3",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueUnits", "id": "valueUnits",
"name": "{i18n:scada.symbol.value-text}", "name": "{i18n:scada.symbol.value-text}",
"type": "units", "type": "units",
"default": "gal", "default": "gal",
"required": null,
"subLabel": "{i18n:scada.symbol.units}", "subLabel": "{i18n:scada.symbol.units}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueTextFont", "id": "valueTextFont",
@ -365,80 +328,78 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueTextColor", "id": "valueTextColor",
"name": "{i18n:scada.symbol.value-text}", "name": "{i18n:scada.symbol.value-text}",
"type": "color", "type": "color",
"default": "#0000008A", "default": "#0000008A",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "scale", "id": "scale",
"name": "{i18n:scada.symbol.scale}", "name": "{i18n:scada.symbol.scale}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null, },
"fieldSuffix": null, {
"disableOnProperty": null, "id": "scaleDisplayFormat",
"rowClass": "", "name": "{i18n:scada.symbol.scale}",
"fieldClass": "", "type": "select",
"min": null, "default": true,
"max": null, "subLabel": "{i18n:scada.symbol.display-format}",
"step": null "disableOnProperty": "scale",
"items": [
{
"value": true,
"label": "Percentage"
},
{
"value": false,
"label": "Absolute"
}
],
"disabled": false,
"visible": true
}, },
{ {
"id": "transparent", "id": "transparent",
"name": "{i18n:scada.symbol.transparent-mode}", "name": "{i18n:scada.symbol.transparent-mode}",
"type": "switch", "type": "switch",
"default": false, "default": false,
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorIntervals", "id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}", "name": "{i18n:scada.symbol.major-ticks}",
"type": "number", "type": "number",
"default": 10, "default": 10,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "step": 1,
"step": 1 "disabled": false,
"visible": true
},
{
"id": "majorUnits",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "units",
"subLabel": "{i18n:scada.symbol.units}",
"divider": true,
"disableOnProperty": "scale",
"disabled": false,
"visible": true
}, },
{ {
"id": "majorFont", "id": "majorFont",
@ -451,128 +412,84 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorColor", "id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#00000061", "default": "#00000061",
"required": null,
"subLabel": "{i18n:scada.symbol.normal}", "subLabel": "{i18n:scada.symbol.normal}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorWarningColor", "id": "majorWarningColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorCriticalColor", "id": "majorCriticalColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorIntervals", "id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}", "name": "{i18n:scada.symbol.minor-ticks}",
"type": "number", "type": "number",
"default": 5, "default": 5,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "disabled": false,
"step": null "visible": true
}, },
{ {
"id": "minorColor", "id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#0000001F", "default": "#0000001F",
"required": null,
"subLabel": "{i18n:scada.symbol.normal}", "subLabel": "{i18n:scada.symbol.normal}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorWarningColor", "id": "minorWarningColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorCriticalColor", "id": "minorCriticalColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
} }
] ]
}]]></tb:metadata> }]]></tb:metadata>

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 118 KiB

219
application/src/main/data/json/system/scada_symbols/stand-horizontal-tank.svg

@ -34,7 +34,7 @@
}, },
{ {
"tag": "scale", "tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 17;\n var majorIntervalLength = 568 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(715, y, 747, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 705, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(727, minorY, 747, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 17;\n var majorIntervalLength = 568 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n var tankCapacity = ctx.properties.scaleDisplayFormat ? 100 : (ctx.values.tankCapacity || 100);\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(715, y, 747, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = ctx.api.formatValue((tankCapacity - i * (tankCapacity/majorIntervals)).toFixed(0), 0, ctx.properties.majorUnits, false);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 705, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(727, minorY, 747, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null "actions": null
}, },
{ {
@ -279,80 +279,43 @@
"name": "{i18n:scada.symbol.tank-color}", "name": "{i18n:scada.symbol.tank-color}",
"type": "color", "type": "color",
"default": "#E5E5E5", "default": "#E5E5E5",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "fluidColor", "id": "fluidColor",
"name": "{i18n:scada.symbol.fluid-color}", "name": "{i18n:scada.symbol.fluid-color}",
"type": "color", "type": "color",
"default": "#1EC1F480", "default": "#1EC1F480",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueBox", "id": "valueBox",
"name": "{i18n:scada.symbol.value-box}", "name": "{i18n:scada.symbol.value-box}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueBoxColor", "id": "valueBoxColor",
"name": "{i18n:scada.symbol.value-box}", "name": "{i18n:scada.symbol.value-box}",
"type": "color", "type": "color",
"default": "#F3F3F3", "default": "#F3F3F3",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueUnits", "id": "valueUnits",
"name": "{i18n:scada.symbol.value-text}", "name": "{i18n:scada.symbol.value-text}",
"type": "units", "type": "units",
"default": "gal", "default": "gal",
"required": null,
"subLabel": "{i18n:scada.symbol.units}", "subLabel": "{i18n:scada.symbol.units}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueTextFont", "id": "valueTextFont",
@ -365,80 +328,78 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueTextColor", "id": "valueTextColor",
"name": "{i18n:scada.symbol.value-text}", "name": "{i18n:scada.symbol.value-text}",
"type": "color", "type": "color",
"default": "#0000008A", "default": "#0000008A",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "scale", "id": "scale",
"name": "{i18n:scada.symbol.scale}", "name": "{i18n:scada.symbol.scale}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null, },
"fieldSuffix": null, {
"disableOnProperty": null, "id": "scaleDisplayFormat",
"rowClass": "", "name": "{i18n:scada.symbol.scale}",
"fieldClass": "", "type": "select",
"min": null, "default": true,
"max": null, "subLabel": "{i18n:scada.symbol.display-format}",
"step": null "disableOnProperty": "scale",
"items": [
{
"value": true,
"label": "Percentage"
},
{
"value": false,
"label": "Absolute"
}
],
"disabled": false,
"visible": true
}, },
{ {
"id": "transparent", "id": "transparent",
"name": "{i18n:scada.symbol.transparent-mode}", "name": "{i18n:scada.symbol.transparent-mode}",
"type": "switch", "type": "switch",
"default": false, "default": false,
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorIntervals", "id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}", "name": "{i18n:scada.symbol.major-ticks}",
"type": "number", "type": "number",
"default": 10, "default": 10,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "step": 1,
"step": 1 "disabled": false,
"visible": true
},
{
"id": "majorUnits",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "units",
"subLabel": "{i18n:scada.symbol.units}",
"divider": true,
"disableOnProperty": "scale",
"disabled": false,
"visible": true
}, },
{ {
"id": "majorFont", "id": "majorFont",
@ -451,128 +412,84 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorColor", "id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#00000061", "default": "#00000061",
"required": null,
"subLabel": "{i18n:scada.symbol.normal}", "subLabel": "{i18n:scada.symbol.normal}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorWarningColor", "id": "majorWarningColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorCriticalColor", "id": "majorCriticalColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorIntervals", "id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}", "name": "{i18n:scada.symbol.minor-ticks}",
"type": "number", "type": "number",
"default": 5, "default": 5,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "disabled": false,
"step": null "visible": true
}, },
{ {
"id": "minorColor", "id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#0000001F", "default": "#0000001F",
"required": null,
"subLabel": "{i18n:scada.symbol.normal}", "subLabel": "{i18n:scada.symbol.normal}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorWarningColor", "id": "minorWarningColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorCriticalColor", "id": "minorCriticalColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
} }
] ]
}]]></tb:metadata> }]]></tb:metadata>

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 120 KiB

219
application/src/main/data/json/system/scada_symbols/stand-vertical-short-tank.svg

@ -35,7 +35,7 @@
}, },
{ {
"tag": "scale", "tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 137;\n var majorIntervalLength = 442 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(523, y, 555, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 513, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(535, minorY, 555, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 137;\n var majorIntervalLength = 442 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n var tankCapacity = ctx.properties.scaleDisplayFormat ? 100 : (ctx.values.tankCapacity || 100);\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(523, y, 555, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = ctx.api.formatValue((tankCapacity - i * (tankCapacity/majorIntervals)).toFixed(0), 0, ctx.properties.majorUnits, false);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 513, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(535, minorY, 555, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null "actions": null
}, },
{ {
@ -280,80 +280,43 @@
"name": "{i18n:scada.symbol.tank-color}", "name": "{i18n:scada.symbol.tank-color}",
"type": "color", "type": "color",
"default": "#E5E5E5", "default": "#E5E5E5",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "fluidColor", "id": "fluidColor",
"name": "{i18n:scada.symbol.fluid-color}", "name": "{i18n:scada.symbol.fluid-color}",
"type": "color", "type": "color",
"default": "#1EC1F480", "default": "#1EC1F480",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueBox", "id": "valueBox",
"name": "{i18n:scada.symbol.value-box}", "name": "{i18n:scada.symbol.value-box}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueBoxColor", "id": "valueBoxColor",
"name": "{i18n:scada.symbol.value-box}", "name": "{i18n:scada.symbol.value-box}",
"type": "color", "type": "color",
"default": "#F3F3F3", "default": "#F3F3F3",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueUnits", "id": "valueUnits",
"name": "{i18n:scada.symbol.value-text}", "name": "{i18n:scada.symbol.value-text}",
"type": "units", "type": "units",
"default": "gal", "default": "gal",
"required": null,
"subLabel": "{i18n:scada.symbol.units}", "subLabel": "{i18n:scada.symbol.units}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueTextFont", "id": "valueTextFont",
@ -366,80 +329,78 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueTextColor", "id": "valueTextColor",
"name": "{i18n:scada.symbol.value-text}", "name": "{i18n:scada.symbol.value-text}",
"type": "color", "type": "color",
"default": "#0000008A", "default": "#0000008A",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "scale", "id": "scale",
"name": "{i18n:scada.symbol.scale}", "name": "{i18n:scada.symbol.scale}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null, },
"fieldSuffix": null, {
"disableOnProperty": null, "id": "scaleDisplayFormat",
"rowClass": "", "name": "{i18n:scada.symbol.scale}",
"fieldClass": "", "type": "select",
"min": null, "default": true,
"max": null, "subLabel": "{i18n:scada.symbol.display-format}",
"step": null "disableOnProperty": "scale",
"items": [
{
"value": true,
"label": "Percentage"
},
{
"value": false,
"label": "Absolute"
}
],
"disabled": false,
"visible": true
}, },
{ {
"id": "transparent", "id": "transparent",
"name": "{i18n:scada.symbol.transparent-mode}", "name": "{i18n:scada.symbol.transparent-mode}",
"type": "switch", "type": "switch",
"default": false, "default": false,
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorIntervals", "id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}", "name": "{i18n:scada.symbol.major-ticks}",
"type": "number", "type": "number",
"default": 5, "default": 5,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "step": 1,
"step": 1 "disabled": false,
"visible": true
},
{
"id": "majorUnits",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "units",
"subLabel": "{i18n:scada.symbol.units}",
"divider": true,
"disableOnProperty": "scale",
"disabled": false,
"visible": true
}, },
{ {
"id": "majorFont", "id": "majorFont",
@ -452,128 +413,84 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorColor", "id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#00000061", "default": "#00000061",
"required": null,
"subLabel": "{i18n:scada.symbol.normal}", "subLabel": "{i18n:scada.symbol.normal}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorWarningColor", "id": "majorWarningColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorCriticalColor", "id": "majorCriticalColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorIntervals", "id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}", "name": "{i18n:scada.symbol.minor-ticks}",
"type": "number", "type": "number",
"default": 5, "default": 5,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "disabled": false,
"step": null "visible": true
}, },
{ {
"id": "minorColor", "id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#0000001F", "default": "#0000001F",
"required": null,
"subLabel": "{i18n:scada.symbol.normal}", "subLabel": "{i18n:scada.symbol.normal}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorWarningColor", "id": "minorWarningColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorCriticalColor", "id": "minorCriticalColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
} }
] ]
}]]></tb:metadata> }]]></tb:metadata>

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 107 KiB

217
application/src/main/data/json/system/scada_symbols/stand-vertical-tank.svg

@ -34,7 +34,7 @@
}, },
{ {
"tag": "scale", "tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 205;\n var majorIntervalLength = 760 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(340, y, 372, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 330, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(352, minorY, 372, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 205;\n var majorIntervalLength = 760 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n var tankCapacity = ctx.properties.scaleDisplayFormat ? 100 : (ctx.values.tankCapacity || 100);\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(340, y, 372, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = ctx.api.formatValue((tankCapacity - i * (tankCapacity/majorIntervals)).toFixed(0), 0, ctx.properties.majorUnits, false);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 330, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(352, minorY, 372, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null "actions": null
}, },
{ {
@ -279,80 +279,43 @@
"name": "{i18n:scada.symbol.tank-color}", "name": "{i18n:scada.symbol.tank-color}",
"type": "color", "type": "color",
"default": "#E5E5E5", "default": "#E5E5E5",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "fluidColor", "id": "fluidColor",
"name": "{i18n:scada.symbol.fluid-color}", "name": "{i18n:scada.symbol.fluid-color}",
"type": "color", "type": "color",
"default": "#1EC1F480", "default": "#1EC1F480",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueBox", "id": "valueBox",
"name": "{i18n:scada.symbol.value-box}", "name": "{i18n:scada.symbol.value-box}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueBoxColor", "id": "valueBoxColor",
"name": "{i18n:scada.symbol.value-box}", "name": "{i18n:scada.symbol.value-box}",
"type": "color", "type": "color",
"default": "#F3F3F3", "default": "#F3F3F3",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueUnits", "id": "valueUnits",
"name": "{i18n:scada.symbol.value-text}", "name": "{i18n:scada.symbol.value-text}",
"type": "units", "type": "units",
"default": "gal", "default": "gal",
"required": null,
"subLabel": "{i18n:scada.symbol.units}", "subLabel": "{i18n:scada.symbol.units}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueTextFont", "id": "valueTextFont",
@ -365,80 +328,76 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueTextColor", "id": "valueTextColor",
"name": "{i18n:scada.symbol.value-text}", "name": "{i18n:scada.symbol.value-text}",
"type": "color", "type": "color",
"default": "#0000008A", "default": "#0000008A",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "scale", "id": "scale",
"name": "{i18n:scada.symbol.scale}", "name": "{i18n:scada.symbol.scale}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null, },
"fieldSuffix": null, {
"disableOnProperty": null, "id": "scaleDisplayFormat",
"rowClass": "", "name": "{i18n:scada.symbol.scale}",
"fieldClass": "", "type": "select",
"min": null, "default": true,
"max": null, "subLabel": "{i18n:scada.symbol.display-format}",
"step": null "disableOnProperty": "scale",
"items": [
{
"value": true,
"label": "Percentage"
},
{
"value": false,
"label": "Absolute"
}
]
}, },
{ {
"id": "transparent", "id": "transparent",
"name": "{i18n:scada.symbol.transparent-mode}", "name": "{i18n:scada.symbol.transparent-mode}",
"type": "switch", "type": "switch",
"default": false, "default": false,
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorIntervals", "id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}", "name": "{i18n:scada.symbol.major-ticks}",
"type": "number", "type": "number",
"default": 10, "default": 10,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "step": 1,
"step": 1 "disabled": false,
"visible": true
},
{
"id": "majorUnits",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "units",
"subLabel": "{i18n:scada.symbol.units}",
"divider": true,
"disableOnProperty": "scale",
"disabled": false,
"visible": true
}, },
{ {
"id": "majorFont", "id": "majorFont",
@ -451,128 +410,84 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorColor", "id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#00000061", "default": "#00000061",
"required": null,
"subLabel": "{i18n:scada.symbol.normal}", "subLabel": "{i18n:scada.symbol.normal}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorWarningColor", "id": "majorWarningColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorCriticalColor", "id": "majorCriticalColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorIntervals", "id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}", "name": "{i18n:scada.symbol.minor-ticks}",
"type": "number", "type": "number",
"default": 5, "default": 5,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "disabled": false,
"step": null "visible": true
}, },
{ {
"id": "minorColor", "id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#0000001F", "default": "#0000001F",
"required": null,
"subLabel": "{i18n:scada.symbol.normal}", "subLabel": "{i18n:scada.symbol.normal}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorWarningColor", "id": "minorWarningColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorCriticalColor", "id": "minorCriticalColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
} }
] ]
}]]></tb:metadata> }]]></tb:metadata>

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 119 KiB

220
application/src/main/data/json/system/scada_symbols/vertical-short-tank.svg

@ -34,7 +34,7 @@
}, },
{ {
"tag": "scale", "tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 137;\n var majorIntervalLength = 442 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(523, y, 555, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 513, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(535, minorY, 555, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 137;\n var majorIntervalLength = 442 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n var tankCapacity = ctx.properties.scaleDisplayFormat ? 100 : (ctx.values.tankCapacity || 100);\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(523, y, 555, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = ctx.api.formatValue((tankCapacity - i * (tankCapacity/majorIntervals)).toFixed(0), 0, ctx.properties.majorUnits, false);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 513, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(535, minorY, 555, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null "actions": null
}, },
{ {
@ -279,80 +279,43 @@
"name": "{i18n:scada.symbol.tank-color}", "name": "{i18n:scada.symbol.tank-color}",
"type": "color", "type": "color",
"default": "#E5E5E5", "default": "#E5E5E5",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "fluidColor", "id": "fluidColor",
"name": "{i18n:scada.symbol.fluid-color}", "name": "{i18n:scada.symbol.fluid-color}",
"type": "color", "type": "color",
"default": "#1EC1F480", "default": "#1EC1F480",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueBox", "id": "valueBox",
"name": "{i18n:scada.symbol.value-box}", "name": "{i18n:scada.symbol.value-box}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueBoxColor", "id": "valueBoxColor",
"name": "{i18n:scada.symbol.value-box}", "name": "{i18n:scada.symbol.value-box}",
"type": "color", "type": "color",
"default": "#F3F3F3", "default": "#F3F3F3",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueUnits", "id": "valueUnits",
"name": "{i18n:scada.symbol.value-text}", "name": "{i18n:scada.symbol.value-text}",
"type": "units", "type": "units",
"default": "gal", "default": "gal",
"required": null,
"subLabel": "{i18n:scada.symbol.units}", "subLabel": "{i18n:scada.symbol.units}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueTextFont", "id": "valueTextFont",
@ -365,80 +328,79 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueTextColor", "id": "valueTextColor",
"name": "{i18n:scada.symbol.value-text}", "name": "{i18n:scada.symbol.value-text}",
"type": "color", "type": "color",
"default": "#0000008A", "default": "#0000008A",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "scale", "id": "scale",
"name": "{i18n:scada.symbol.scale}", "name": "{i18n:scada.symbol.scale}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null, },
"fieldSuffix": null, {
"disableOnProperty": null, "id": "scaleDisplayFormat",
"rowClass": "", "name": "{i18n:scada.symbol.scale}",
"fieldClass": "", "type": "select",
"min": null, "default": true,
"max": null, "subLabel": "{i18n:scada.symbol.display-format}",
"step": null "disableOnProperty": "scale",
"items": [
{
"value": true,
"label": "Percentage"
},
{
"value": false,
"label": "Absolute"
}
],
"disabled": false,
"visible": true
}, },
{ {
"id": "transparent", "id": "transparent",
"name": "{i18n:scada.symbol.transparent-mode}", "name": "{i18n:scada.symbol.transparent-mode}",
"type": "switch", "type": "switch",
"default": false, "default": false,
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorIntervals", "id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}", "name": "{i18n:scada.symbol.major-ticks}",
"type": "number", "type": "number",
"default": 5, "default": 5,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "step": 1,
"step": 1 "disabled": false,
"visible": true
},
{
"id": "majorUnits",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "units",
"required": false,
"subLabel": "{i18n:scada.symbol.units}",
"divider": true,
"disableOnProperty": "scale",
"disabled": false,
"visible": true
}, },
{ {
"id": "majorFont", "id": "majorFont",
@ -451,128 +413,84 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorColor", "id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#00000061", "default": "#00000061",
"required": null,
"subLabel": "{i18n:scada.symbol.normal}", "subLabel": "{i18n:scada.symbol.normal}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorWarningColor", "id": "majorWarningColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorCriticalColor", "id": "majorCriticalColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorIntervals", "id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}", "name": "{i18n:scada.symbol.minor-ticks}",
"type": "number", "type": "number",
"default": 5, "default": 5,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "disabled": false,
"step": null "visible": true
}, },
{ {
"id": "minorColor", "id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#0000001F", "default": "#0000001F",
"required": null,
"subLabel": "{i18n:scada.symbol.normal}", "subLabel": "{i18n:scada.symbol.normal}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorWarningColor", "id": "minorWarningColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorCriticalColor", "id": "minorCriticalColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
} }
] ]
}]]></tb:metadata> }]]></tb:metadata>

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 100 KiB

140
application/src/main/data/json/system/scada_symbols/vertical-tank-hp.svg

@ -38,7 +38,7 @@
}, },
{ {
"tag": "scale", "tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 3;\n var majorIntervalLength = 994 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(160, y, 192, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n if (i === 0) {\n majorTickText.attr({x: 150, y: y + 10, 'text-anchor': 'end', class: 'majorTickText'});\n } else if (i === majorIntervals) {\n majorTickText.attr({x: 150, y: y - 5, 'text-anchor': 'end', class: 'majorTickText'});\n } else {\n majorTickText.attr({x: 150, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n }\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(172, minorY, 192, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 3;\n var majorIntervalLength = 994 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n var tankCapacity = ctx.properties.scaleDisplayFormat ? 100 : (ctx.values.tankCapacity || 100);\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(160, y, 192, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = ctx.api.formatValue((tankCapacity - i * (tankCapacity/majorIntervals)).toFixed(0), 0, ctx.properties.majorUnits, false);\n var majorTickText = ctx.svg.text(majorText);\n if (i === 0) {\n majorTickText.attr({x: 150, y: y + 10, 'text-anchor': 'end', class: 'majorTickText'});\n } else if (i === majorIntervals) {\n majorTickText.attr({x: 150, y: y - 5, 'text-anchor': 'end', class: 'majorTickText'});\n } else {\n majorTickText.attr({x: 150, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n }\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(172, minorY, 192, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null "actions": null
}, },
{ {
@ -307,64 +307,67 @@
"name": "{i18n:scada.symbol.tank-color}", "name": "{i18n:scada.symbol.tank-color}",
"type": "color", "type": "color",
"default": "#EBEBEB", "default": "#EBEBEB",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "fluidColor", "id": "fluidColor",
"name": "{i18n:scada.symbol.fluid-color}", "name": "{i18n:scada.symbol.fluid-color}",
"type": "color", "type": "color",
"default": "#C8DFF7", "default": "#C8DFF7",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "scale", "id": "scale",
"name": "{i18n:scada.symbol.scale}", "name": "{i18n:scada.symbol.scale}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null, },
"fieldSuffix": null, {
"disableOnProperty": null, "id": "scaleDisplayFormat",
"rowClass": "", "name": "{i18n:scada.symbol.scale}",
"fieldClass": "", "type": "select",
"min": null, "default": true,
"max": null, "subLabel": "{i18n:scada.symbol.display-format}",
"step": null "disableOnProperty": "scale",
"items": [
{
"value": true,
"label": "Percentage"
},
{
"value": false,
"label": "Absolute"
}
],
"disabled": false,
"visible": true
}, },
{ {
"id": "majorIntervals", "id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}", "name": "{i18n:scada.symbol.major-ticks}",
"type": "number", "type": "number",
"default": 10, "default": 10,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": false, "divider": false,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "step": 1,
"step": 1 "disabled": false,
"visible": true
},
{
"id": "majorUnits",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "units",
"subLabel": "{i18n:scada.symbol.units}",
"divider": true,
"disableOnProperty": "scale",
"disabled": false,
"visible": true
}, },
{ {
"id": "majorFont", "id": "majorFont",
@ -377,96 +380,57 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorColor", "id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks}", "name": "{i18n:scada.symbol.major-ticks}",
"type": "color", "type": "color",
"default": "#00000061", "default": "#00000061",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorIntervals", "id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}", "name": "{i18n:scada.symbol.minor-ticks}",
"type": "number", "type": "number",
"default": 5, "default": 5,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "disabled": false,
"step": null "visible": true
}, },
{ {
"id": "minorColor", "id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks}", "name": "{i18n:scada.symbol.minor-ticks}",
"type": "color", "type": "color",
"default": "#0000001F", "default": "#0000001F",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "warningColor", "id": "warningColor",
"name": "{i18n:scada.symbol.alarm-colors}", "name": "{i18n:scada.symbol.alarm-colors}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null, "disabled": false,
"disableOnProperty": null, "visible": true
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "criticalColor", "id": "criticalColor",
"name": "{i18n:scada.symbol.alarm-colors}", "name": "{i18n:scada.symbol.alarm-colors}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null, "disabled": false,
"fieldSuffix": null, "visible": true
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
} }
] ]
}]]></tb:metadata> }]]></tb:metadata>

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

220
application/src/main/data/json/system/scada_symbols/vertical-tank.svg

@ -33,7 +33,7 @@
}, },
{ {
"tag": "scale", "tag": "scale",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 205;\n var majorIntervalLength = 760 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(340, y, 372, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 330, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(352, minorY, 372, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 205;\n var majorIntervalLength = 760 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n var tankCapacity = ctx.properties.scaleDisplayFormat ? 100 : (ctx.values.tankCapacity || 100);\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(340, y, 372, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = ctx.api.formatValue((tankCapacity - i * (tankCapacity/majorIntervals)).toFixed(0), 0, ctx.properties.majorUnits, false);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 330, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(352, minorY, 372, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null "actions": null
}, },
{ {
@ -278,80 +278,43 @@
"name": "{i18n:scada.symbol.tank-color}", "name": "{i18n:scada.symbol.tank-color}",
"type": "color", "type": "color",
"default": "#E5E5E5", "default": "#E5E5E5",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "fluidColor", "id": "fluidColor",
"name": "{i18n:scada.symbol.fluid-color}", "name": "{i18n:scada.symbol.fluid-color}",
"type": "color", "type": "color",
"default": "#1EC1F480", "default": "#1EC1F480",
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueBox", "id": "valueBox",
"name": "{i18n:scada.symbol.value-box}", "name": "{i18n:scada.symbol.value-box}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueBoxColor", "id": "valueBoxColor",
"name": "{i18n:scada.symbol.value-box}", "name": "{i18n:scada.symbol.value-box}",
"type": "color", "type": "color",
"default": "#F3F3F3", "default": "#F3F3F3",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueUnits", "id": "valueUnits",
"name": "{i18n:scada.symbol.value-text}", "name": "{i18n:scada.symbol.value-text}",
"type": "units", "type": "units",
"default": "gal", "default": "gal",
"required": null,
"subLabel": "{i18n:scada.symbol.units}", "subLabel": "{i18n:scada.symbol.units}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueTextFont", "id": "valueTextFont",
@ -364,80 +327,79 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "valueTextColor", "id": "valueTextColor",
"name": "{i18n:scada.symbol.value-text}", "name": "{i18n:scada.symbol.value-text}",
"type": "color", "type": "color",
"default": "#0000008A", "default": "#0000008A",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "valueBox", "disableOnProperty": "valueBox",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "scale", "id": "scale",
"name": "{i18n:scada.symbol.scale}", "name": "{i18n:scada.symbol.scale}",
"type": "switch", "type": "switch",
"default": true, "default": true,
"required": null, "disabled": false,
"subLabel": null, "visible": true
"divider": null, },
"fieldSuffix": null, {
"disableOnProperty": null, "id": "scaleDisplayFormat",
"rowClass": "", "name": "{i18n:scada.symbol.scale}",
"fieldClass": "", "type": "select",
"min": null, "default": true,
"max": null, "subLabel": "{i18n:scada.symbol.display-format}",
"step": null "divider": false,
"disableOnProperty": "scale",
"items": [
{
"value": true,
"label": "Percentage"
},
{
"value": false,
"label": "Absolute"
}
],
"disabled": false,
"visible": true
}, },
{ {
"id": "transparent", "id": "transparent",
"name": "{i18n:scada.symbol.transparent-mode}", "name": "{i18n:scada.symbol.transparent-mode}",
"type": "switch", "type": "switch",
"default": false, "default": false,
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorIntervals", "id": "majorIntervals",
"name": "{i18n:scada.symbol.major-ticks}", "name": "{i18n:scada.symbol.major-ticks}",
"type": "number", "type": "number",
"default": 10, "default": 10,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "step": 1,
"step": 1 "disabled": false,
"visible": true
},
{
"id": "majorUnits",
"name": "{i18n:scada.symbol.major-ticks}",
"type": "units",
"subLabel": "{i18n:scada.symbol.units}",
"divider": true,
"disableOnProperty": "scale",
"disabled": false,
"visible": true
}, },
{ {
"id": "majorFont", "id": "majorFont",
@ -450,128 +412,84 @@
"weight": "500", "weight": "500",
"style": "normal" "style": "normal"
}, },
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorColor", "id": "majorColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#00000061", "default": "#00000061",
"required": null,
"subLabel": "{i18n:scada.symbol.normal}", "subLabel": "{i18n:scada.symbol.normal}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorWarningColor", "id": "majorWarningColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "majorCriticalColor", "id": "majorCriticalColor",
"name": "{i18n:scada.symbol.major-ticks-color}", "name": "{i18n:scada.symbol.major-ticks-color}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorIntervals", "id": "minorIntervals",
"name": "{i18n:scada.symbol.minor-ticks}", "name": "{i18n:scada.symbol.minor-ticks}",
"type": "number", "type": "number",
"default": 5, "default": 5,
"required": null,
"subLabel": "{i18n:scada.symbol.intervals}", "subLabel": "{i18n:scada.symbol.intervals}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "",
"fieldClass": "",
"min": 1, "min": 1,
"max": null, "disabled": false,
"step": null "visible": true
}, },
{ {
"id": "minorColor", "id": "minorColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#0000001F", "default": "#0000001F",
"required": null,
"subLabel": "{i18n:scada.symbol.normal}", "subLabel": "{i18n:scada.symbol.normal}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorWarningColor", "id": "minorWarningColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#FAA405", "default": "#FAA405",
"required": null,
"subLabel": "{i18n:scada.symbol.warning}", "subLabel": "{i18n:scada.symbol.warning}",
"divider": true, "divider": true,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
}, },
{ {
"id": "minorCriticalColor", "id": "minorCriticalColor",
"name": "{i18n:scada.symbol.minor-ticks-color}", "name": "{i18n:scada.symbol.minor-ticks-color}",
"type": "color", "type": "color",
"default": "#D12730", "default": "#D12730",
"required": null,
"subLabel": "{i18n:scada.symbol.critical}", "subLabel": "{i18n:scada.symbol.critical}",
"divider": null,
"fieldSuffix": null,
"disableOnProperty": "scale", "disableOnProperty": "scale",
"rowClass": "", "disabled": false,
"fieldClass": "", "visible": true
"min": null,
"max": null,
"step": null
} }
] ]
}]]></tb:metadata> }]]></tb:metadata>

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 112 KiB

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

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/horizontal_ellipse_tank.json

@ -17,7 +17,7 @@
"settingsDirective": "tb-liquid-level-card-widget-settings", "settingsDirective": "tb-liquid-level-card-widget-settings",
"hasBasicMode": true, "hasBasicMode": true,
"basicModeDirective": "tb-liquid-level-card-basic-config", "basicModeDirective": "tb-liquid-level-card-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"return Math.floor(Math.random() * 101);\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"tankSelectionType\":\"Static\",\"selectedShape\":\"Horizontal Ellipse\",\"shapeAttributeName\":\"tankShape\",\"tankColor\":{\"type\":\"range\",\"color\":\"#242770\",\"rangeList\":[{\"from\":null,\"to\":20,\"color\":\"#E73535DE\"},{\"from\":20,\"to\":null,\"color\":\"#242770\"}],\"colorFunction\":\"var percent = value;\\nif (typeof percent !== undefined) {\\n if (percent < 20) {\\n return '#E73535DE';\\n }\\n}\\nreturn '#242770';\"},\"datasourceUnits\":\"%\",\"layout\":\"percentage\",\"volumeSource\":\"static\",\"volumeConstant\":500,\"volumeAttributeName\":\"volume\",\"volumeUnits\":\"L\",\"volumeFont\":{\"family\":\"Roboto\",\"size\":14,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"100%\"},\"volumeColor\":\"rgba(0, 0, 0, 0.18)\",\"units\":\"%\",\"widgetUnitsSource\":\"static\",\"widgetUnitsAttributeName\":\"units\",\"liquidColor\":{\"type\":\"range\",\"color\":\"#7A8BFF\",\"rangeList\":[{\"from\":null,\"to\":20,\"color\":\"#E27C7CDE\"},{\"from\":20,\"to\":null,\"color\":\"#7A8BFF\"}],\"colorFunction\":\"var percent = value;\\nif (typeof percent !== undefined) {\\n if (percent < 20) {\\n return '#E27C7CDE';\\n }\\n}\\nreturn '#7A8BFF';\"},\"valueFont\":{\"family\":\"Roboto\",\"size\":24,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"100%\"},\"valueColor\":{\"type\":\"range\",\"color\":\"#000000DE\",\"rangeList\":[{\"from\":null,\"to\":20,\"color\":\"#FF0000DE\"},{\"from\":20,\"to\":null,\"color\":\"rgba(0,0,0,0.87)\"}],\"colorFunction\":\"var percent = value;\\nif (typeof percent !== undefined) {\\n if (percent < 20) {\\n return '#FF0000DE';\\n }\\n}\\nreturn '#000000DE';\"},\"showBackgroundOverlay\":true,\"backgroundOverlayColor\":{\"type\":\"range\",\"color\":\"#FFFFFFC2\",\"rangeList\":[{\"from\":0,\"to\":20,\"color\":\"#FFEFEFDE\"},{\"from\":20,\"to\":null,\"color\":\"#FFFFFFC2\"}],\"colorFunction\":\"var percent = value;\\nif (typeof percent !== undefined) {\\n if (percent < 20) {\\n return '#FFEFEFDE';\\n }\\n}\\nreturn '#FFFFFFC2';\"},\"showTooltip\":true,\"showTooltipLevel\":true,\"tooltipUnits\":\"%\",\"tooltipLevelDecimals\":0,\"tooltipLevelFont\":{\"family\":\"Roboto\",\"size\":13,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"100%\"},\"tooltipLevelColor\":{\"type\":\"constant\",\"color\":\"rgba(0, 0, 0, 0.76)\",\"rangeList\":[],\"colorFunction\":\"var percent = value;\\nif (typeof percent !== undefined) {\\n if (percent < 20) {\\n return '#E27C7CDE';\\n }\\n}\\nreturn '#7A8BFF';\"},\"showTooltipDate\":true,\"tooltipDateFormat\":{\"format\":null,\"lastUpdateAgo\":true,\"custom\":false},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":13,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"100%\"},\"tooltipDateColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":3,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Liquid level\",\"useDashboardTimewindow\":true,\"displayTimewindow\":true,\"configMode\":\"basic\",\"titleFont\":{\"family\":\"Roboto\",\"size\":16,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"1.5\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"showTitleIcon\":false,\"titleIcon\":\"water_drop\",\"iconColor\":\"#5469FF\",\"decimals\":0,\"enableDataExport\":false,\"enableFullscreen\":false,\"borderRadius\":\"0px\",\"actions\":{},\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":true,\"margin\":\"0px\",\"widgetStyle\":{},\"widgetCss\":\"\",\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"pageSize\":1024,\"noDataDisplayMessage\":\"\"}" "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"return Math.floor(Math.random() * 101);\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"tankSelectionType\":\"static\",\"selectedShape\":\"Horizontal Ellipse\",\"shapeAttributeName\":\"tankShape\",\"tankColor\":{\"type\":\"range\",\"color\":\"#242770\",\"rangeList\":[{\"from\":null,\"to\":20,\"color\":\"#E73535DE\"},{\"from\":20,\"to\":null,\"color\":\"#242770\"}],\"colorFunction\":\"var percent = value;\\nif (typeof percent !== undefined) {\\n if (percent < 20) {\\n return '#E73535DE';\\n }\\n}\\nreturn '#242770';\"},\"datasourceUnits\":\"%\",\"layout\":\"percentage\",\"volumeSource\":\"static\",\"volumeConstant\":500,\"volumeAttributeName\":\"volume\",\"volumeUnits\":\"L\",\"volumeFont\":{\"family\":\"Roboto\",\"size\":14,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"100%\"},\"volumeColor\":\"rgba(0, 0, 0, 0.18)\",\"units\":\"%\",\"widgetUnitsSource\":\"static\",\"widgetUnitsAttributeName\":\"units\",\"liquidColor\":{\"type\":\"range\",\"color\":\"#7A8BFF\",\"rangeList\":[{\"from\":null,\"to\":20,\"color\":\"#E27C7CDE\"},{\"from\":20,\"to\":null,\"color\":\"#7A8BFF\"}],\"colorFunction\":\"var percent = value;\\nif (typeof percent !== undefined) {\\n if (percent < 20) {\\n return '#E27C7CDE';\\n }\\n}\\nreturn '#7A8BFF';\"},\"valueFont\":{\"family\":\"Roboto\",\"size\":24,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"100%\"},\"valueColor\":{\"type\":\"range\",\"color\":\"#000000DE\",\"rangeList\":[{\"from\":null,\"to\":20,\"color\":\"#FF0000DE\"},{\"from\":20,\"to\":null,\"color\":\"rgba(0,0,0,0.87)\"}],\"colorFunction\":\"var percent = value;\\nif (typeof percent !== undefined) {\\n if (percent < 20) {\\n return '#FF0000DE';\\n }\\n}\\nreturn '#000000DE';\"},\"showBackgroundOverlay\":true,\"backgroundOverlayColor\":{\"type\":\"range\",\"color\":\"#FFFFFFC2\",\"rangeList\":[{\"from\":0,\"to\":20,\"color\":\"#FFEFEFDE\"},{\"from\":20,\"to\":null,\"color\":\"#FFFFFFC2\"}],\"colorFunction\":\"var percent = value;\\nif (typeof percent !== undefined) {\\n if (percent < 20) {\\n return '#FFEFEFDE';\\n }\\n}\\nreturn '#FFFFFFC2';\"},\"showTooltip\":true,\"showTooltipLevel\":true,\"tooltipUnits\":\"%\",\"tooltipLevelDecimals\":0,\"tooltipLevelFont\":{\"family\":\"Roboto\",\"size\":13,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"100%\"},\"tooltipLevelColor\":{\"type\":\"constant\",\"color\":\"rgba(0, 0, 0, 0.76)\",\"rangeList\":[],\"colorFunction\":\"var percent = value;\\nif (typeof percent !== undefined) {\\n if (percent < 20) {\\n return '#E27C7CDE';\\n }\\n}\\nreturn '#7A8BFF';\"},\"showTooltipDate\":true,\"tooltipDateFormat\":{\"format\":null,\"lastUpdateAgo\":true,\"custom\":false},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":13,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"100%\"},\"tooltipDateColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":3,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Liquid level\",\"useDashboardTimewindow\":true,\"displayTimewindow\":true,\"configMode\":\"basic\",\"titleFont\":{\"family\":\"Roboto\",\"size\":16,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"1.5\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"showTitleIcon\":false,\"titleIcon\":\"water_drop\",\"iconColor\":\"#5469FF\",\"decimals\":0,\"enableDataExport\":false,\"enableFullscreen\":false,\"borderRadius\":\"0px\",\"actions\":{},\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":true,\"margin\":\"0px\",\"widgetStyle\":{},\"widgetCss\":\"\",\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"pageSize\":1024,\"noDataDisplayMessage\":\"\"}"
}, },
"tags": [ "tags": [
"reservoir", "reservoir",

26
application/src/main/data/json/system/widget_types/image_map.json

File diff suppressed because one or more lines are too long

32
application/src/main/data/json/system/widget_types/map.json

File diff suppressed because one or more lines are too long

36
application/src/main/data/json/system/widget_types/route_map.json

File diff suppressed because one or more lines are too long

37
application/src/main/data/json/system/widget_types/trip_map.json

File diff suppressed because one or more lines are too long

5
application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java

@ -141,6 +141,7 @@ import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -856,9 +857,9 @@ public class ActorSystemContext {
appActor.tellWithHighPriority(tbActorMsg); appActor.tellWithHighPriority(tbActorMsg);
} }
public void schedulePeriodicMsgWithDelay(TbActorRef ctx, TbActorMsg msg, long delayInMs, long periodInMs) { public ScheduledFuture<?> schedulePeriodicMsgWithDelay(TbActorRef ctx, TbActorMsg msg, long delayInMs, long periodInMs) {
log.debug("Scheduling periodic msg {} every {} ms with delay {} ms", msg, periodInMs, delayInMs); log.debug("Scheduling periodic msg {} every {} ms with delay {} ms", msg, periodInMs, delayInMs);
getScheduler().scheduleWithFixedDelay(() -> ctx.tell(msg), delayInMs, periodInMs, TimeUnit.MILLISECONDS); return getScheduler().scheduleWithFixedDelay(() -> ctx.tell(msg), delayInMs, periodInMs, TimeUnit.MILLISECONDS);
} }
public void scheduleMsgWithDelay(TbActorRef ctx, TbActorMsg msg, long delayInMs) { public void scheduleMsgWithDelay(TbActorRef ctx, TbActorMsg msg, long delayInMs) {

1
application/src/main/java/org/thingsboard/server/actors/app/AppActor.java

@ -116,7 +116,6 @@ public class AppActor extends ContextAwareActor {
case CF_INIT_MSG: case CF_INIT_MSG:
case CF_LINK_INIT_MSG: case CF_LINK_INIT_MSG:
case CF_STATE_RESTORE_MSG: case CF_STATE_RESTORE_MSG:
case CF_ENTITY_LIFECYCLE_MSG:
//TODO: use priority from the message body. For example, messages about CF lifecycle are important and Device lifecycle are not. //TODO: use priority from the message body. For example, messages about CF lifecycle are important and Device lifecycle are not.
// same for the Linked telemetry. // same for the Linked telemetry.
onToCalculatedFieldSystemActorMsg((ToCalculatedFieldSystemMsg) msg, true); onToCalculatedFieldSystemActorMsg((ToCalculatedFieldSystemMsg) msg, true);

39
application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java

@ -22,6 +22,7 @@ import org.thingsboard.server.actors.TbActorRef;
import org.thingsboard.server.actors.TbCalculatedFieldEntityActorId; import org.thingsboard.server.actors.TbCalculatedFieldEntityActorId;
import org.thingsboard.server.actors.service.DefaultActorService; import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor; import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.cf.CalculatedField; import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.cf.CalculatedFieldLink; import org.thingsboard.server.common.data.cf.CalculatedFieldLink;
@ -35,6 +36,7 @@ import org.thingsboard.server.common.msg.cf.CalculatedFieldInitMsg;
import org.thingsboard.server.common.msg.cf.CalculatedFieldLinkInitMsg; import org.thingsboard.server.common.msg.cf.CalculatedFieldLinkInitMsg;
import org.thingsboard.server.common.msg.cf.CalculatedFieldPartitionChangeMsg; import org.thingsboard.server.common.msg.cf.CalculatedFieldPartitionChangeMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.dao.cf.CalculatedFieldService; import org.thingsboard.server.dao.cf.CalculatedFieldService;
import org.thingsboard.server.service.cf.CalculatedFieldProcessingService; import org.thingsboard.server.service.cf.CalculatedFieldProcessingService;
@ -121,7 +123,7 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
if (calculatedField != null) { if (calculatedField != null) {
msg.getState().setRequiredArguments(calculatedField.getArgNames()); msg.getState().setRequiredArguments(calculatedField.getArgNames());
log.info("Pushing CF state restore msg to specific actor [{}]", msg.getId().entityId()); log.debug("Pushing CF state restore msg to specific actor [{}]", msg.getId().entityId());
getOrCreateActor(msg.getId().entityId()).tell(msg); getOrCreateActor(msg.getId().entityId()).tell(msg);
} else { } else {
cfStateService.removeState(msg.getId(), msg.getCallback()); cfStateService.removeState(msg.getId(), msg.getCallback());
@ -178,6 +180,9 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
EntityId entityId = msg.getEntityId(); EntityId entityId = msg.getEntityId();
EntityId profileId = getProfileId(tenantId, entityId); EntityId profileId = getProfileId(tenantId, entityId);
cfEntityCache.add(tenantId, profileId, entityId); cfEntityCache.add(tenantId, profileId, entityId);
if (!isMyPartition(entityId, callback)) {
return;
}
var entityIdFields = getCalculatedFieldsByEntityId(entityId); var entityIdFields = getCalculatedFieldsByEntityId(entityId);
var profileIdFields = getCalculatedFieldsByEntityId(profileId); var profileIdFields = getCalculatedFieldsByEntityId(profileId);
var fieldsCount = entityIdFields.size() + profileIdFields.size(); var fieldsCount = entityIdFields.size() + profileIdFields.size();
@ -191,8 +196,11 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
} }
private void onEntityUpdated(ComponentLifecycleMsg msg, TbCallback callback) { private void onEntityUpdated(ComponentLifecycleMsg msg, TbCallback callback) {
if (msg.getOldProfileId() != null && msg.getOldProfileId() != msg.getProfileId()) { if (msg.getOldProfileId() != null && !msg.getOldProfileId().equals(msg.getProfileId())) {
cfEntityCache.update(tenantId, msg.getOldProfileId(), msg.getProfileId(), msg.getEntityId()); cfEntityCache.update(tenantId, msg.getOldProfileId(), msg.getProfileId(), msg.getEntityId());
if (!isMyPartition(msg.getEntityId(), callback)) {
return;
}
var oldProfileCfs = getCalculatedFieldsByEntityId(msg.getOldProfileId()); var oldProfileCfs = getCalculatedFieldsByEntityId(msg.getOldProfileId());
var newProfileCfs = getCalculatedFieldsByEntityId(msg.getProfileId()); var newProfileCfs = getCalculatedFieldsByEntityId(msg.getProfileId());
var fieldsCount = oldProfileCfs.size() + newProfileCfs.size(); var fieldsCount = oldProfileCfs.size() + newProfileCfs.size();
@ -209,8 +217,10 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
private void onEntityDeleted(ComponentLifecycleMsg msg, TbCallback callback) { private void onEntityDeleted(ComponentLifecycleMsg msg, TbCallback callback) {
cfEntityCache.evict(tenantId, msg.getEntityId()); cfEntityCache.evict(tenantId, msg.getEntityId());
log.info("Pushing entity lifecycle msg to specific actor [{}]", msg.getEntityId()); if (isMyPartition(msg.getEntityId(), callback)) {
getOrCreateActor(msg.getEntityId()).tell(new CalculatedFieldEntityDeleteMsg(tenantId, msg.getEntityId(), callback)); log.debug("Pushing entity lifecycle msg to specific actor [{}]", msg.getEntityId());
getOrCreateActor(msg.getEntityId()).tell(new CalculatedFieldEntityDeleteMsg(tenantId, msg.getEntityId(), callback));
}
} }
private void onCfCreated(ComponentLifecycleMsg msg, TbCallback callback) throws CalculatedFieldException { private void onCfCreated(ComponentLifecycleMsg msg, TbCallback callback) throws CalculatedFieldException {
@ -311,7 +321,9 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
callback.onSuccess(); callback.onSuccess();
} }
} else { } else {
deleteCfForEntity(entityId, cfId, callback); if (isMyPartition(entityId, callback)) {
deleteCfForEntity(entityId, cfId, callback);
}
} }
} }
} }
@ -418,20 +430,31 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
callback.onSuccess(); callback.onSuccess();
} }
} else { } else {
initCfForEntity(entityId, cfCtx, forceStateReinit, callback); if (isMyPartition(entityId, callback)) {
initCfForEntity(entityId, cfCtx, forceStateReinit, callback);
}
} }
} }
private void deleteCfForEntity(EntityId entityId, CalculatedFieldId cfId, TbCallback callback) { private void deleteCfForEntity(EntityId entityId, CalculatedFieldId cfId, TbCallback callback) {
log.info("Pushing delete CF msg to specific actor [{}]", entityId); log.debug("Pushing delete CF msg to specific actor [{}]", entityId);
getOrCreateActor(entityId).tell(new CalculatedFieldEntityDeleteMsg(tenantId, cfId, callback)); getOrCreateActor(entityId).tell(new CalculatedFieldEntityDeleteMsg(tenantId, cfId, callback));
} }
private void initCfForEntity(EntityId entityId, CalculatedFieldCtx cfCtx, boolean forceStateReinit, TbCallback callback) { private void initCfForEntity(EntityId entityId, CalculatedFieldCtx cfCtx, boolean forceStateReinit, TbCallback callback) {
log.info("Pushing entity init CF msg to specific actor [{}]", entityId); log.debug("Pushing entity init CF msg to specific actor [{}]", entityId);
getOrCreateActor(entityId).tell(new EntityInitCalculatedFieldMsg(tenantId, cfCtx, callback, forceStateReinit)); getOrCreateActor(entityId).tell(new EntityInitCalculatedFieldMsg(tenantId, cfCtx, callback, forceStateReinit));
} }
private boolean isMyPartition(EntityId entityId, TbCallback callback) {
if (!systemContext.getPartitionService().resolve(ServiceType.TB_RULE_ENGINE, DataConstants.CF_QUEUE_NAME, tenantId, entityId).isMyPartition()) {
log.debug("[{}] Entity belongs to external partition.", entityId);
callback.onSuccess();
return false;
}
return true;
}
private static boolean isProfileEntity(EntityType entityType) { private static boolean isProfileEntity(EntityType entityType) {
return EntityType.DEVICE_PROFILE.equals(entityType) || EntityType.ASSET_PROFILE.equals(entityType); return EntityType.DEVICE_PROFILE.equals(entityType) || EntityType.ASSET_PROFILE.equals(entityType);
} }

10
application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java

@ -29,6 +29,9 @@ import org.thingsboard.server.common.msg.TbActorStopReason;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
import java.util.Optional;
import java.util.concurrent.ScheduledFuture;
/** /**
* @author Andrew Shvayka * @author Andrew Shvayka
*/ */
@ -41,6 +44,7 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP
protected P processor; protected P processor;
private long messagesProcessed; private long messagesProcessed;
private long errorsOccurred; private long errorsOccurred;
ScheduledFuture<?> statsScheduledFuture = null;
public ComponentActor(ActorSystemContext systemContext, TenantId tenantId, T id) { public ComponentActor(ActorSystemContext systemContext, TenantId tenantId, T id) {
super(systemContext); super(systemContext);
@ -73,9 +77,9 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP
} }
} }
private void scheduleStatsPersistTick() { void scheduleStatsPersistTick() {
try { try {
processor.scheduleStatsPersistTick(ctx, systemContext.getStatisticsPersistFrequency()); this.statsScheduledFuture = processor.scheduleStatsPersistTick(ctx, systemContext.getStatisticsPersistFrequency());
} catch (Exception e) { } catch (Exception e) {
log.error("[{}][{}] Failed to schedule statistics store message. No statistics is going to be stored: {}", tenantId, id, e.getMessage()); log.error("[{}][{}] Failed to schedule statistics store message. No statistics is going to be stored: {}", tenantId, id, e.getMessage());
logAndPersist("onScheduleStatsPersistMsg", e); logAndPersist("onScheduleStatsPersistMsg", e);
@ -90,6 +94,8 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP
processor.stop(ctx); processor.stop(ctx);
} }
logLifecycleEvent(ComponentLifecycleEvent.STOPPED); logLifecycleEvent(ComponentLifecycleEvent.STOPPED);
Optional.ofNullable(statsScheduledFuture).ifPresent(x -> x.cancel(false));
statsScheduledFuture = null;
} catch (Exception e) { } catch (Exception e) {
log.warn("[{}][{}] Failed to stop {} processor: {}", tenantId, id, id.getEntityType(), e.getMessage()); log.warn("[{}][{}] Failed to stop {} processor: {}", tenantId, id, id.getEntityType(), e.getMessage());
logAndPersist("OnStop", e, true); logAndPersist("OnStop", e, true);

5
application/src/main/java/org/thingsboard/server/actors/shared/AbstractContextAwareMsgProcessor.java

@ -21,6 +21,7 @@ import org.thingsboard.server.actors.TbActorCtx;
import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.TbActorMsg;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
@Slf4j @Slf4j
public abstract class AbstractContextAwareMsgProcessor { public abstract class AbstractContextAwareMsgProcessor {
@ -36,8 +37,8 @@ public abstract class AbstractContextAwareMsgProcessor {
return systemContext.getScheduler(); return systemContext.getScheduler();
} }
protected void schedulePeriodicMsgWithDelay(TbActorCtx ctx, TbActorMsg msg, long delayInMs, long periodInMs) { protected ScheduledFuture<?> schedulePeriodicMsgWithDelay(TbActorCtx ctx, TbActorMsg msg, long delayInMs, long periodInMs) {
systemContext.schedulePeriodicMsgWithDelay(ctx, msg, delayInMs, periodInMs); return systemContext.schedulePeriodicMsgWithDelay(ctx, msg, delayInMs, periodInMs);
} }
protected void scheduleMsgWithDelay(TbActorCtx ctx, TbActorMsg msg, long delayInMs) { protected void scheduleMsgWithDelay(TbActorCtx ctx, TbActorMsg msg, long delayInMs) {

6
application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java

@ -27,6 +27,8 @@ import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
import org.thingsboard.server.common.msg.queue.RuleNodeException; import org.thingsboard.server.common.msg.queue.RuleNodeException;
import java.util.concurrent.ScheduledFuture;
@Slf4j @Slf4j
public abstract class ComponentMsgProcessor<T extends EntityId> extends AbstractContextAwareMsgProcessor { public abstract class ComponentMsgProcessor<T extends EntityId> extends AbstractContextAwareMsgProcessor {
@ -77,8 +79,8 @@ public abstract class ComponentMsgProcessor<T extends EntityId> extends Abstract
start(context); start(context);
} }
public void scheduleStatsPersistTick(TbActorCtx context, long statsPersistFrequency) { public ScheduledFuture<?> scheduleStatsPersistTick(TbActorCtx context, long statsPersistFrequency) {
schedulePeriodicMsgWithDelay(context, StatsPersistTick.INSTANCE, statsPersistFrequency, statsPersistFrequency); return schedulePeriodicMsgWithDelay(context, StatsPersistTick.INSTANCE, statsPersistFrequency, statsPersistFrequency);
} }
protected boolean checkMsgValid(TbMsg tbMsg) { protected boolean checkMsgValid(TbMsg tbMsg) {

33
application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java

@ -49,6 +49,7 @@ import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg; import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
import org.thingsboard.server.common.msg.aware.DeviceAwareMsg; import org.thingsboard.server.common.msg.aware.DeviceAwareMsg;
import org.thingsboard.server.common.msg.aware.RuleChainAwareMsg; import org.thingsboard.server.common.msg.aware.RuleChainAwareMsg;
import org.thingsboard.server.common.msg.cf.CalculatedFieldEntityLifecycleMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg; import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
@ -112,7 +113,7 @@ public class TenantActor extends RuleChainManagerActor {
cantFindTenant = true; cantFindTenant = true;
} }
} else { } else {
log.info("Tenant {} is not managed by current service, skipping rule chains init", tenantId); log.info("Tenant {} is not managed by current service, skipping rule chains and cf actor init", tenantId);
} }
} }
log.debug("[{}] Tenant actor started.", tenantId); log.debug("[{}] Tenant actor started.", tenantId);
@ -175,7 +176,6 @@ public class TenantActor extends RuleChainManagerActor {
case CF_LINK_INIT_MSG: case CF_LINK_INIT_MSG:
case CF_STATE_RESTORE_MSG: case CF_STATE_RESTORE_MSG:
case CF_PARTITIONS_CHANGE_MSG: case CF_PARTITIONS_CHANGE_MSG:
case CF_ENTITY_LIFECYCLE_MSG:
onToCalculatedFieldSystemActorMsg((ToCalculatedFieldSystemMsg) msg, true); onToCalculatedFieldSystemActorMsg((ToCalculatedFieldSystemMsg) msg, true);
break; break;
case CF_TELEMETRY_MSG: case CF_TELEMETRY_MSG:
@ -315,19 +315,26 @@ public class TenantActor extends RuleChainManagerActor {
onToDeviceActorMsg(new DeviceDeleteMsg(tenantId, deviceId), true); onToDeviceActorMsg(new DeviceDeleteMsg(tenantId, deviceId), true);
deletedDevices.add(deviceId); deletedDevices.add(deviceId);
} }
if (isRuleEngine && ruleChainsInitialized) { if (isRuleEngine) {
TbActorRef target = getEntityActorRef(msg.getEntityId()); if (ruleChainsInitialized) {
if (target != null) { TbActorRef target = getEntityActorRef(msg.getEntityId());
if (msg.getEntityId().getEntityType() == EntityType.RULE_CHAIN) { if (target != null) {
RuleChain ruleChain = systemContext.getRuleChainService(). if (msg.getEntityId().getEntityType() == EntityType.RULE_CHAIN) {
findRuleChainById(tenantId, new RuleChainId(msg.getEntityId().getId())); RuleChain ruleChain = systemContext.getRuleChainService().
if (ruleChain != null && RuleChainType.CORE.equals(ruleChain.getType())) { findRuleChainById(tenantId, new RuleChainId(msg.getEntityId().getId()));
visit(ruleChain, target); if (ruleChain != null && RuleChainType.CORE.equals(ruleChain.getType())) {
visit(ruleChain, target);
}
} }
target.tellWithHighPriority(msg);
} else {
log.debug("[{}] Invalid component lifecycle msg: {}", tenantId, msg);
}
}
if (cfActor != null) {
if (msg.getEntityId().getEntityType().isOneOf(EntityType.CALCULATED_FIELD, EntityType.DEVICE, EntityType.ASSET)) {
cfActor.tellWithHighPriority(new CalculatedFieldEntityLifecycleMsg(tenantId, msg));
} }
target.tellWithHighPriority(msg);
} else {
log.debug("[{}] Invalid component lifecycle msg: {}", tenantId, msg);
} }
} }
} }

51
application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java

@ -54,10 +54,12 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.msg.tools.SchedulerUtils; import org.thingsboard.server.common.msg.tools.SchedulerUtils;
import org.thingsboard.server.common.util.ProtoUtils;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; import org.thingsboard.server.dao.usagerecord.ApiUsageStateService;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto; import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
@ -144,18 +146,40 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
} }
@Override @Override
public void process(TbProtoQueueMsg<ToUsageStatsServiceMsg> msg, TbCallback callback) { public void process(TbProtoQueueMsg<ToUsageStatsServiceMsg> msgPack, TbCallback callback) {
ToUsageStatsServiceMsg statsMsg = msg.getValue(); ToUsageStatsServiceMsg serviceMsg = msgPack.getValue();
String serviceId = serviceMsg.getServiceId();
TenantId tenantId = TenantId.fromUUID(new UUID(statsMsg.getTenantIdMSB(), statsMsg.getTenantIdLSB()));
EntityId ownerId; List<TransportProtos.UsageStatsServiceMsg> msgs;
if (statsMsg.getCustomerIdMSB() != 0 && statsMsg.getCustomerIdLSB() != 0) {
ownerId = new CustomerId(new UUID(statsMsg.getCustomerIdMSB(), statsMsg.getCustomerIdLSB())); //For backward compatibility, remove after release
if (serviceMsg.getMsgsList().isEmpty()) {
TransportProtos.UsageStatsServiceMsg oldMsg = TransportProtos.UsageStatsServiceMsg.newBuilder()
.setTenantIdMSB(serviceMsg.getTenantIdMSB())
.setTenantIdLSB(serviceMsg.getTenantIdLSB())
.setCustomerIdMSB(serviceMsg.getCustomerIdMSB())
.setCustomerIdLSB(serviceMsg.getCustomerIdLSB())
.setEntityIdMSB(serviceMsg.getEntityIdMSB())
.setEntityIdLSB(serviceMsg.getEntityIdLSB())
.addAllValues(serviceMsg.getValuesList())
.build();
msgs = List.of(oldMsg);
} else { } else {
ownerId = tenantId; msgs = serviceMsg.getMsgsList();
} }
processEntityUsageStats(tenantId, ownerId, statsMsg.getValuesList(), statsMsg.getServiceId()); msgs.forEach(msg -> {
TenantId tenantId = TenantId.fromUUID(new UUID(msg.getTenantIdMSB(), msg.getTenantIdLSB()));
EntityId ownerId;
if (msg.getCustomerIdMSB() != 0 && msg.getCustomerIdLSB() != 0) {
ownerId = new CustomerId(new UUID(msg.getCustomerIdMSB(), msg.getCustomerIdLSB()));
} else {
ownerId = tenantId;
}
processEntityUsageStats(tenantId, ownerId, msg.getValuesList(), serviceId);
});
callback.onSuccess(); callback.onSuccess();
} }
@ -181,7 +205,14 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
updatedEntries = new ArrayList<>(ApiUsageRecordKey.values().length); updatedEntries = new ArrayList<>(ApiUsageRecordKey.values().length);
Set<ApiFeature> apiFeatures = new HashSet<>(); Set<ApiFeature> apiFeatures = new HashSet<>();
for (UsageStatsKVProto statsItem : values) { for (UsageStatsKVProto statsItem : values) {
ApiUsageRecordKey recordKey = ApiUsageRecordKey.valueOf(statsItem.getKey()); ApiUsageRecordKey recordKey;
//For backward compatibility, remove after release
if (StringUtils.isNotEmpty(statsItem.getKey())) {
recordKey = ApiUsageRecordKey.valueOf(statsItem.getKey());
} else {
recordKey = ProtoUtils.fromProto(statsItem.getRecordKey());
}
StatsCalculationResult calculationResult = usageState.calculate(recordKey, statsItem.getValue(), serviceId); StatsCalculationResult calculationResult = usageState.calculate(recordKey, statsItem.getValue(), serviceId);
if (calculationResult.isValueChanged()) { if (calculationResult.isValueChanged()) {

35
application/src/main/java/org/thingsboard/server/service/cf/AbstractCalculatedFieldStateService.java

@ -19,14 +19,20 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.calculatedField.CalculatedFieldStateRestoreMsg; import org.thingsboard.server.actors.calculatedField.CalculatedFieldStateRestoreMsg;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.exception.CalculatedFieldStateException; import org.thingsboard.server.exception.CalculatedFieldStateException;
import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldStateProto; import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldStateProto;
import org.thingsboard.server.gen.transport.TransportProtos.ToCalculatedFieldMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCalculatedFieldMsg;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.common.consumer.PartitionedQueueConsumerManager; import org.thingsboard.server.queue.common.state.QueueStateService;
import org.thingsboard.server.queue.discovery.QueueKey;
import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId; import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId;
import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldState; import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldState;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;
import static org.thingsboard.server.utils.CalculatedFieldUtils.fromProto; import static org.thingsboard.server.utils.CalculatedFieldUtils.fromProto;
import static org.thingsboard.server.utils.CalculatedFieldUtils.toProto; import static org.thingsboard.server.utils.CalculatedFieldUtils.toProto;
@ -35,12 +41,7 @@ public abstract class AbstractCalculatedFieldStateService implements CalculatedF
@Autowired @Autowired
private ActorSystemContext actorSystemContext; private ActorSystemContext actorSystemContext;
protected PartitionedQueueConsumerManager<TbProtoQueueMsg<ToCalculatedFieldMsg>> eventConsumer; protected QueueStateService<TbProtoQueueMsg<ToCalculatedFieldMsg>, TbProtoQueueMsg<CalculatedFieldStateProto>> stateService;
@Override
public void init(PartitionedQueueConsumerManager<TbProtoQueueMsg<ToCalculatedFieldMsg>> eventConsumer) {
this.eventConsumer = eventConsumer;
}
@Override @Override
public final void persistState(CalculatedFieldEntityCtxId stateId, CalculatedFieldState state, TbCallback callback) { public final void persistState(CalculatedFieldEntityCtxId stateId, CalculatedFieldState state, TbCallback callback) {
@ -69,4 +70,24 @@ public abstract class AbstractCalculatedFieldStateService implements CalculatedF
actorSystemContext.tell(new CalculatedFieldStateRestoreMsg(id, state)); actorSystemContext.tell(new CalculatedFieldStateRestoreMsg(id, state));
} }
@Override
public void restore(QueueKey queueKey, Set<TopicPartitionInfo> partitions) {
stateService.update(queueKey, partitions);
}
@Override
public void delete(Set<TopicPartitionInfo> partitions) {
stateService.delete(partitions);
}
@Override
public Set<TopicPartitionInfo> getPartitions() {
return stateService.getPartitions().values().stream().flatMap(Collection::stream).collect(Collectors.toSet());
}
@Override
public void stop() {
stateService.stop();
}
} }

7
application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldStateService.java

@ -21,6 +21,7 @@ import org.thingsboard.server.exception.CalculatedFieldStateException;
import org.thingsboard.server.gen.transport.TransportProtos.ToCalculatedFieldMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCalculatedFieldMsg;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.common.consumer.PartitionedQueueConsumerManager; import org.thingsboard.server.queue.common.consumer.PartitionedQueueConsumerManager;
import org.thingsboard.server.queue.discovery.QueueKey;
import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId; import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId;
import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldState; import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldState;
@ -34,7 +35,11 @@ public interface CalculatedFieldStateService {
void removeState(CalculatedFieldEntityCtxId stateId, TbCallback callback); void removeState(CalculatedFieldEntityCtxId stateId, TbCallback callback);
void restore(Set<TopicPartitionInfo> partitions); void restore(QueueKey queueKey, Set<TopicPartitionInfo> partitions);
void delete(Set<TopicPartitionInfo> partitions);
Set<TopicPartitionInfo> getPartitions();
void stop(); void stop();

7
application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldQueueService.java

@ -128,6 +128,9 @@ public class DefaultCalculatedFieldQueueService implements CalculatedFieldQueueS
private void checkEntityAndPushToQueue(TenantId tenantId, EntityId entityId, private void checkEntityAndPushToQueue(TenantId tenantId, EntityId entityId,
Predicate<CalculatedFieldCtx> mainEntityFilter, Predicate<CalculatedFieldCtx> linkedEntityFilter, Predicate<CalculatedFieldCtx> mainEntityFilter, Predicate<CalculatedFieldCtx> linkedEntityFilter,
Supplier<ToCalculatedFieldMsg> msg, FutureCallback<Void> callback) { Supplier<ToCalculatedFieldMsg> msg, FutureCallback<Void> callback) {
if (EntityType.TENANT.equals(entityId.getEntityType())) {
tenantId = (TenantId) entityId;
}
boolean send = checkEntityForCalculatedFields(tenantId, entityId, mainEntityFilter, linkedEntityFilter); boolean send = checkEntityForCalculatedFields(tenantId, entityId, mainEntityFilter, linkedEntityFilter);
if (send) { if (send) {
clusterService.pushMsgToCalculatedFields(tenantId, entityId, msg.get(), wrap(callback)); clusterService.pushMsgToCalculatedFields(tenantId, entityId, msg.get(), wrap(callback));
@ -221,6 +224,10 @@ public class DefaultCalculatedFieldQueueService implements CalculatedFieldQueueS
private CalculatedFieldTelemetryMsgProto.Builder buildTelemetryMsgProto(TenantId tenantId, EntityId entityId, List<CalculatedFieldId> calculatedFieldIds, UUID tbMsgId, TbMsgType tbMsgType) { private CalculatedFieldTelemetryMsgProto.Builder buildTelemetryMsgProto(TenantId tenantId, EntityId entityId, List<CalculatedFieldId> calculatedFieldIds, UUID tbMsgId, TbMsgType tbMsgType) {
CalculatedFieldTelemetryMsgProto.Builder telemetryMsg = CalculatedFieldTelemetryMsgProto.newBuilder(); CalculatedFieldTelemetryMsgProto.Builder telemetryMsg = CalculatedFieldTelemetryMsgProto.newBuilder();
if (EntityType.TENANT.equals(entityId.getEntityType())) {
tenantId = (TenantId) entityId;
}
telemetryMsg.setTenantIdMSB(tenantId.getId().getMostSignificantBits()); telemetryMsg.setTenantIdMSB(tenantId.getId().getMostSignificantBits());
telemetryMsg.setTenantIdLSB(tenantId.getId().getLeastSignificantBits()); telemetryMsg.setTenantIdLSB(tenantId.getId().getLeastSignificantBits());

26
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/KafkaCalculatedFieldStateService.java

@ -35,7 +35,7 @@ import org.thingsboard.server.queue.TbQueueMsgHeaders;
import org.thingsboard.server.queue.TbQueueMsgMetadata; import org.thingsboard.server.queue.TbQueueMsgMetadata;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.common.consumer.PartitionedQueueConsumerManager; import org.thingsboard.server.queue.common.consumer.PartitionedQueueConsumerManager;
import org.thingsboard.server.queue.common.consumer.QueueStateService; import org.thingsboard.server.queue.common.state.KafkaQueueStateService;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.QueueKey; import org.thingsboard.server.queue.discovery.QueueKey;
import org.thingsboard.server.queue.kafka.TbKafkaProducerTemplate; import org.thingsboard.server.queue.kafka.TbKafkaProducerTemplate;
@ -43,10 +43,12 @@ import org.thingsboard.server.queue.provider.TbRuleEngineQueueFactory;
import org.thingsboard.server.service.cf.AbstractCalculatedFieldStateService; import org.thingsboard.server.service.cf.AbstractCalculatedFieldStateService;
import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId; import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import static org.thingsboard.server.queue.common.AbstractTbQueueTemplate.*; import static org.thingsboard.server.queue.common.AbstractTbQueueTemplate.bytesToString;
import static org.thingsboard.server.queue.common.AbstractTbQueueTemplate.bytesToUuid;
import static org.thingsboard.server.queue.common.AbstractTbQueueTemplate.stringToBytes;
import static org.thingsboard.server.queue.common.AbstractTbQueueTemplate.uuidToBytes;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@ -60,18 +62,14 @@ public class KafkaCalculatedFieldStateService extends AbstractCalculatedFieldSta
@Value("${queue.calculated_fields.poll_interval:25}") @Value("${queue.calculated_fields.poll_interval:25}")
private long pollInterval; private long pollInterval;
private PartitionedQueueConsumerManager<TbProtoQueueMsg<CalculatedFieldStateProto>> stateConsumer;
private TbKafkaProducerTemplate<TbProtoQueueMsg<CalculatedFieldStateProto>> stateProducer; private TbKafkaProducerTemplate<TbProtoQueueMsg<CalculatedFieldStateProto>> stateProducer;
private QueueStateService<TbProtoQueueMsg<ToCalculatedFieldMsg>, TbProtoQueueMsg<CalculatedFieldStateProto>> queueStateService;
private final AtomicInteger counter = new AtomicInteger(); private final AtomicInteger counter = new AtomicInteger();
@Override @Override
public void init(PartitionedQueueConsumerManager<TbProtoQueueMsg<ToCalculatedFieldMsg>> eventConsumer) { public void init(PartitionedQueueConsumerManager<TbProtoQueueMsg<ToCalculatedFieldMsg>> eventConsumer) {
super.init(eventConsumer);
var queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, DataConstants.CF_STATES_QUEUE_NAME); var queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, DataConstants.CF_STATES_QUEUE_NAME);
this.stateConsumer = PartitionedQueueConsumerManager.<TbProtoQueueMsg<CalculatedFieldStateProto>>create() PartitionedQueueConsumerManager<TbProtoQueueMsg<CalculatedFieldStateProto>> stateConsumer = PartitionedQueueConsumerManager.<TbProtoQueueMsg<CalculatedFieldStateProto>>create()
.queueKey(queueKey) .queueKey(queueKey)
.topic(partitionService.getTopic(queueKey)) .topic(partitionService.getTopic(queueKey))
.pollInterval(pollInterval) .pollInterval(pollInterval)
@ -94,13 +92,13 @@ public class KafkaCalculatedFieldStateService extends AbstractCalculatedFieldSta
} }
}) })
.consumerCreator((config, partitionId) -> queueFactory.createCalculatedFieldStateConsumer()) .consumerCreator((config, partitionId) -> queueFactory.createCalculatedFieldStateConsumer())
.queueAdmin(queueFactory.getCalculatedFieldQueueAdmin())
.consumerExecutor(eventConsumer.getConsumerExecutor()) .consumerExecutor(eventConsumer.getConsumerExecutor())
.scheduler(eventConsumer.getScheduler()) .scheduler(eventConsumer.getScheduler())
.taskExecutor(eventConsumer.getTaskExecutor()) .taskExecutor(eventConsumer.getTaskExecutor())
.build(); .build();
super.stateService = new KafkaQueueStateService<>(eventConsumer, stateConsumer);
this.stateProducer = (TbKafkaProducerTemplate<TbProtoQueueMsg<CalculatedFieldStateProto>>) queueFactory.createCalculatedFieldStateProducer(); this.stateProducer = (TbKafkaProducerTemplate<TbProtoQueueMsg<CalculatedFieldStateProto>>) queueFactory.createCalculatedFieldStateProducer();
this.queueStateService = new QueueStateService<>();
this.queueStateService.init(stateConsumer, super.eventConsumer);
} }
@Override @Override
@ -132,11 +130,6 @@ public class KafkaCalculatedFieldStateService extends AbstractCalculatedFieldSta
doPersist(stateId, null, callback); doPersist(stateId, null, callback);
} }
@Override
public void restore(Set<TopicPartitionInfo> partitions) {
queueStateService.update(partitions);
}
private void putStateId(TbQueueMsgHeaders headers, CalculatedFieldEntityCtxId stateId) { private void putStateId(TbQueueMsgHeaders headers, CalculatedFieldEntityCtxId stateId) {
headers.put("tenantId", uuidToBytes(stateId.tenantId().getId())); headers.put("tenantId", uuidToBytes(stateId.tenantId().getId()));
headers.put("cfId", uuidToBytes(stateId.cfId().getId())); headers.put("cfId", uuidToBytes(stateId.cfId().getId()));
@ -153,8 +146,7 @@ public class KafkaCalculatedFieldStateService extends AbstractCalculatedFieldSta
@Override @Override
public void stop() { public void stop() {
stateConsumer.stop(); super.stop();
stateConsumer.awaitStop();
stateProducer.stop(); stateProducer.stop();
} }

21
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/RocksDBCalculatedFieldStateService.java

@ -22,7 +22,12 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.queue.common.state.DefaultQueueStateService;
import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldStateProto; import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldStateProto;
import org.thingsboard.server.gen.transport.TransportProtos.ToCalculatedFieldMsg;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.common.consumer.PartitionedQueueConsumerManager;
import org.thingsboard.server.queue.discovery.QueueKey;
import org.thingsboard.server.service.cf.AbstractCalculatedFieldStateService; import org.thingsboard.server.service.cf.AbstractCalculatedFieldStateService;
import org.thingsboard.server.service.cf.CfRocksDb; import org.thingsboard.server.service.cf.CfRocksDb;
import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId; import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId;
@ -37,7 +42,10 @@ public class RocksDBCalculatedFieldStateService extends AbstractCalculatedFieldS
private final CfRocksDb cfRocksDb; private final CfRocksDb cfRocksDb;
private boolean initialized; @Override
public void init(PartitionedQueueConsumerManager<TbProtoQueueMsg<ToCalculatedFieldMsg>> eventConsumer) {
super.stateService = new DefaultQueueStateService<>(eventConsumer);
}
@Override @Override
protected void doPersist(CalculatedFieldEntityCtxId stateId, CalculatedFieldStateProto stateMsgProto, TbCallback callback) { protected void doPersist(CalculatedFieldEntityCtxId stateId, CalculatedFieldStateProto stateMsgProto, TbCallback callback) {
@ -52,8 +60,8 @@ public class RocksDBCalculatedFieldStateService extends AbstractCalculatedFieldS
} }
@Override @Override
public void restore(Set<TopicPartitionInfo> partitions) { public void restore(QueueKey queueKey, Set<TopicPartitionInfo> partitions) {
if (!this.initialized) { if (stateService.getPartitions().isEmpty()) {
cfRocksDb.forEach((key, value) -> { cfRocksDb.forEach((key, value) -> {
try { try {
processRestoredState(CalculatedFieldStateProto.parseFrom(value)); processRestoredState(CalculatedFieldStateProto.parseFrom(value));
@ -61,13 +69,8 @@ public class RocksDBCalculatedFieldStateService extends AbstractCalculatedFieldS
log.error("[{}] Failed to process restored state", key, e); log.error("[{}] Failed to process restored state", key, e);
} }
}); });
this.initialized = true;
} }
eventConsumer.update(partitions); super.restore(queueKey, partitions);
}
@Override
public void stop() {
} }
} }

57
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCalculatedFieldConsumerService.java

@ -15,31 +15,31 @@
*/ */
package org.thingsboard.server.service.queue; package org.thingsboard.server.service.queue;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy; import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.calculatedField.CalculatedFieldLinkedTelemetryMsg; import org.thingsboard.server.actors.calculatedField.CalculatedFieldLinkedTelemetryMsg;
import org.thingsboard.server.actors.calculatedField.CalculatedFieldTelemetryMsg; import org.thingsboard.server.actors.calculatedField.CalculatedFieldTelemetryMsg;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.queue.QueueConfig; import org.thingsboard.server.common.data.queue.QueueConfig;
import org.thingsboard.server.common.msg.cf.CalculatedFieldEntityLifecycleMsg;
import org.thingsboard.server.common.msg.cf.CalculatedFieldPartitionChangeMsg; import org.thingsboard.server.common.msg.cf.CalculatedFieldPartitionChangeMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldLinkedTelemetryMsgProto; import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldLinkedTelemetryMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldTelemetryMsgProto; import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldTelemetryMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.ComponentLifecycleMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.ToCalculatedFieldMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCalculatedFieldMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToCalculatedFieldNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCalculatedFieldNotificationMsg;
import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueConsumer;
@ -69,8 +69,6 @@ import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.thingsboard.server.common.util.ProtoUtils.fromProto;
@Service @Service
@TbRuleEngineComponent @TbRuleEngineComponent
@Slf4j @Slf4j
@ -84,8 +82,6 @@ public class DefaultTbCalculatedFieldConsumerService extends AbstractConsumerSer
private final TbRuleEngineQueueFactory queueFactory; private final TbRuleEngineQueueFactory queueFactory;
private final CalculatedFieldStateService stateService; private final CalculatedFieldStateService stateService;
private PartitionedQueueConsumerManager<TbProtoQueueMsg<ToCalculatedFieldMsg>> eventConsumer;
public DefaultTbCalculatedFieldConsumerService(TbRuleEngineQueueFactory tbQueueFactory, public DefaultTbCalculatedFieldConsumerService(TbRuleEngineQueueFactory tbQueueFactory,
ActorSystemContext actorContext, ActorSystemContext actorContext,
TbDeviceProfileCache deviceProfileCache, TbDeviceProfileCache deviceProfileCache,
@ -108,12 +104,13 @@ public class DefaultTbCalculatedFieldConsumerService extends AbstractConsumerSer
super.init("tb-cf"); super.init("tb-cf");
var queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, DataConstants.CF_QUEUE_NAME); var queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, DataConstants.CF_QUEUE_NAME);
this.eventConsumer = PartitionedQueueConsumerManager.<TbProtoQueueMsg<ToCalculatedFieldMsg>>create() PartitionedQueueConsumerManager<TbProtoQueueMsg<ToCalculatedFieldMsg>> eventConsumer = PartitionedQueueConsumerManager.<TbProtoQueueMsg<ToCalculatedFieldMsg>>create()
.queueKey(queueKey) .queueKey(queueKey)
.topic(partitionService.getTopic(queueKey)) .topic(partitionService.getTopic(queueKey))
.pollInterval(pollInterval) .pollInterval(pollInterval)
.msgPackProcessor(this::processMsgs) .msgPackProcessor(this::processMsgs)
.consumerCreator((config, partitionId) -> queueFactory.createToCalculatedFieldMsgConsumer()) .consumerCreator((config, partitionId) -> queueFactory.createToCalculatedFieldMsgConsumer())
.queueAdmin(queueFactory.getCalculatedFieldQueueAdmin())
.consumerExecutor(consumersExecutor) .consumerExecutor(consumersExecutor)
.scheduler(scheduler) .scheduler(scheduler)
.taskExecutor(mgmtExecutor) .taskExecutor(mgmtExecutor)
@ -133,9 +130,12 @@ public class DefaultTbCalculatedFieldConsumerService extends AbstractConsumerSer
@Override @Override
protected void onTbApplicationEvent(PartitionChangeEvent event) { protected void onTbApplicationEvent(PartitionChangeEvent event) {
var partitions = event.getCfPartitions();
try { try {
stateService.restore(partitions); event.getNewPartitions().forEach((queueKey, partitions) -> {
if (queueKey.getQueueName().equals(DataConstants.CF_QUEUE_NAME)) {
stateService.restore(queueKey, partitions);
}
});
// eventConsumer's partitions will be updated by stateService // eventConsumer's partitions will be updated by stateService
// Cleanup old entities after corresponding consumers are stopped. // Cleanup old entities after corresponding consumers are stopped.
@ -168,9 +168,6 @@ public class DefaultTbCalculatedFieldConsumerService extends AbstractConsumerSer
forwardToActorSystem(toCfMsg.getTelemetryMsg(), callback); forwardToActorSystem(toCfMsg.getTelemetryMsg(), callback);
} else if (toCfMsg.hasLinkedTelemetryMsg()) { } else if (toCfMsg.hasLinkedTelemetryMsg()) {
forwardToActorSystem(toCfMsg.getLinkedTelemetryMsg(), callback); forwardToActorSystem(toCfMsg.getLinkedTelemetryMsg(), callback);
} else if (toCfMsg.hasComponentLifecycleMsg()) {
log.trace("[{}] Forwarding component lifecycle message for processing {}", id, toCfMsg.getComponentLifecycleMsg());
forwardToActorSystem(toCfMsg.getComponentLifecycleMsg(), callback);
} }
} catch (Throwable e) { } catch (Throwable e) {
log.warn("[{}] Failed to process message: {}", id, msg, e); log.warn("[{}] Failed to process message: {}", id, msg, e);
@ -219,15 +216,26 @@ public class DefaultTbCalculatedFieldConsumerService extends AbstractConsumerSer
@Override @Override
protected void handleNotification(UUID id, TbProtoQueueMsg<ToCalculatedFieldNotificationMsg> msg, TbCallback callback) { protected void handleNotification(UUID id, TbProtoQueueMsg<ToCalculatedFieldNotificationMsg> msg, TbCallback callback) {
ToCalculatedFieldNotificationMsg toCfNotification = msg.getValue(); ToCalculatedFieldNotificationMsg toCfNotification = msg.getValue();
if (toCfNotification.hasComponentLifecycleMsg()) { if (toCfNotification.hasLinkedTelemetryMsg()) {
// from upstream (maybe removed since we don't need to init state for each partition)
log.trace("[{}] Forwarding component lifecycle message for processing {}", id, toCfNotification.getComponentLifecycleMsg());
forwardToActorSystem(toCfNotification.getComponentLifecycleMsg(), callback);
} else if (toCfNotification.hasLinkedTelemetryMsg()) {
forwardToActorSystem(toCfNotification.getLinkedTelemetryMsg(), callback); forwardToActorSystem(toCfNotification.getLinkedTelemetryMsg(), callback);
} }
} }
@EventListener
public void handleComponentLifecycleEvent(ComponentLifecycleMsg event) {
if (event.getEntityId().getEntityType() == EntityType.TENANT) {
if (event.getEvent() == ComponentLifecycleEvent.DELETED) {
Set<TopicPartitionInfo> partitions = stateService.getPartitions();
if (CollectionUtils.isEmpty(partitions)) {
return;
}
stateService.delete(partitions.stream()
.filter(tpi -> tpi.getTenantId().isPresent() && tpi.getTenantId().get().equals(event.getTenantId()))
.collect(Collectors.toSet()));
}
}
}
private void forwardToActorSystem(CalculatedFieldTelemetryMsgProto msg, TbCallback callback) { private void forwardToActorSystem(CalculatedFieldTelemetryMsgProto msg, TbCallback callback) {
var tenantId = toTenantId(msg.getTenantIdMSB(), msg.getTenantIdLSB()); var tenantId = toTenantId(msg.getTenantIdMSB(), msg.getTenantIdLSB());
var entityId = EntityIdFactory.getByTypeAndUuid(msg.getEntityType(), new UUID(msg.getEntityIdMSB(), msg.getEntityIdLSB())); var entityId = EntityIdFactory.getByTypeAndUuid(msg.getEntityType(), new UUID(msg.getEntityIdMSB(), msg.getEntityIdLSB()));
@ -241,11 +249,6 @@ public class DefaultTbCalculatedFieldConsumerService extends AbstractConsumerSer
actorContext.tell(new CalculatedFieldLinkedTelemetryMsg(tenantId, entityId, linkedMsg, callback)); actorContext.tell(new CalculatedFieldLinkedTelemetryMsg(tenantId, entityId, linkedMsg, callback));
} }
private void forwardToActorSystem(ComponentLifecycleMsgProto proto, TbCallback callback) {
var msg = fromProto(proto);
actorContext.tell(new CalculatedFieldEntityLifecycleMsg(msg.getTenantId(), msg, callback));
}
private TenantId toTenantId(long tenantIdMSB, long tenantIdLSB) { private TenantId toTenantId(long tenantIdMSB, long tenantIdLSB) {
return TenantId.fromUUID(new UUID(tenantIdMSB, tenantIdLSB)); return TenantId.fromUUID(new UUID(tenantIdMSB, tenantIdLSB));
} }
@ -253,9 +256,7 @@ public class DefaultTbCalculatedFieldConsumerService extends AbstractConsumerSer
@Override @Override
protected void stopConsumers() { protected void stopConsumers() {
super.stopConsumers(); super.stopConsumers();
eventConsumer.stop(); stateService.stop(); // eventConsumer will be stopped by stateService
eventConsumer.awaitStop();
stateService.stop();
} }
} }

101
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java

@ -94,10 +94,8 @@ import org.thingsboard.server.queue.common.MultipleTbQueueCallbackWrapper;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.common.TbRuleEngineProducerService; import org.thingsboard.server.queue.common.TbRuleEngineProducerService;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.QueueKey;
import org.thingsboard.server.queue.discovery.TopicService; import org.thingsboard.server.queue.discovery.TopicService;
import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.service.cf.CalculatedFieldProcessingService;
import org.thingsboard.server.service.gateway_device.GatewayNotificationsService; import org.thingsboard.server.service.gateway_device.GatewayNotificationsService;
import org.thingsboard.server.service.ota.OtaPackageStateService; import org.thingsboard.server.service.ota.OtaPackageStateService;
import org.thingsboard.server.service.profile.TbAssetProfileCache; import org.thingsboard.server.service.profile.TbAssetProfileCache;
@ -146,10 +144,6 @@ public class DefaultTbClusterService implements TbClusterService {
@Lazy @Lazy
private OtaPackageStateService otaPackageStateService; private OtaPackageStateService otaPackageStateService;
@Autowired
@Lazy
private CalculatedFieldProcessingService calculatedFieldProcessingService;
private final TopicService topicService; private final TopicService topicService;
private final TbDeviceProfileCache deviceProfileCache; private final TbDeviceProfileCache deviceProfileCache;
private final TbAssetProfileCache assetProfileCache; private final TbAssetProfileCache assetProfileCache;
@ -369,13 +363,6 @@ public class DefaultTbClusterService implements TbClusterService {
toRuleEngineMsgs.incrementAndGet(); // TODO: add separate counter when we will have new ServiceType.CALCULATED_FIELDS toRuleEngineMsgs.incrementAndGet(); // TODO: add separate counter when we will have new ServiceType.CALCULATED_FIELDS
} }
@Override
public void pushNotificationToCalculatedFields(TenantId tenantId, EntityId entityId, ToCalculatedFieldNotificationMsg msg, TbQueueCallback callback) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, DataConstants.CF_QUEUE_NAME, tenantId, entityId);
producerProvider.getCalculatedFieldsNotificationsMsgProducer().send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), msg), callback);
toRuleEngineNfs.incrementAndGet();
}
@Override @Override
public void broadcastEntityStateChangeEvent(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent state) { public void broadcastEntityStateChangeEvent(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent state) {
log.trace("[{}] Processing {} state change event: {}", tenantId, entityId.getEntityType(), state); log.trace("[{}] Processing {} state change event: {}", tenantId, entityId.getEntityType(), state);
@ -431,7 +418,6 @@ public class DefaultTbClusterService implements TbClusterService {
public void onDeviceDeleted(TenantId tenantId, Device device, TbQueueCallback callback) { public void onDeviceDeleted(TenantId tenantId, Device device, TbQueueCallback callback) {
DeviceId deviceId = device.getId(); DeviceId deviceId = device.getId();
gatewayNotificationsService.onDeviceDeleted(device); gatewayNotificationsService.onDeviceDeleted(device);
handleCalculatedFieldEntityDeleted(tenantId, deviceId);
broadcastEntityDeleteToTransport(tenantId, deviceId, device.getName(), callback); broadcastEntityDeleteToTransport(tenantId, deviceId, device.getName(), callback);
sendDeviceStateServiceEvent(tenantId, deviceId, false, false, true); sendDeviceStateServiceEvent(tenantId, deviceId, false, false, true);
broadcastEntityStateChangeEvent(tenantId, deviceId, ComponentLifecycleEvent.DELETED); broadcastEntityStateChangeEvent(tenantId, deviceId, ComponentLifecycleEvent.DELETED);
@ -440,7 +426,6 @@ public class DefaultTbClusterService implements TbClusterService {
@Override @Override
public void onAssetDeleted(TenantId tenantId, Asset asset, TbQueueCallback callback) { public void onAssetDeleted(TenantId tenantId, Asset asset, TbQueueCallback callback) {
AssetId assetId = asset.getId(); AssetId assetId = asset.getId();
handleCalculatedFieldEntityDeleted(tenantId, assetId);
broadcastEntityStateChangeEvent(tenantId, assetId, ComponentLifecycleEvent.DELETED); broadcastEntityStateChangeEvent(tenantId, assetId, ComponentLifecycleEvent.DELETED);
} }
@ -604,6 +589,7 @@ public class DefaultTbClusterService implements TbClusterService {
|| (entityType.equals(EntityType.DEVICE) && msg.getEvent() == ComponentLifecycleEvent.UPDATED) || (entityType.equals(EntityType.DEVICE) && msg.getEvent() == ComponentLifecycleEvent.UPDATED)
|| entityType.equals(EntityType.ENTITY_VIEW) || entityType.equals(EntityType.ENTITY_VIEW)
|| entityType.equals(EntityType.NOTIFICATION_RULE) || entityType.equals(EntityType.NOTIFICATION_RULE)
|| entityType.equals(EntityType.CALCULATED_FIELD)
) { ) {
TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> toCoreNfProducer = producerProvider.getTbCoreNotificationsMsgProducer(); TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> toCoreNfProducer = producerProvider.getTbCoreNotificationsMsgProducer();
Set<String> tbCoreServices = partitionService.getAllServiceIds(ServiceType.TB_CORE); Set<String> tbCoreServices = partitionService.getAllServiceIds(ServiceType.TB_CORE);
@ -658,38 +644,28 @@ public class DefaultTbClusterService implements TbClusterService {
public void onDeviceUpdated(Device entity, Device old) { public void onDeviceUpdated(Device entity, Device old) {
var created = old == null; var created = old == null;
broadcastEntityChangeToTransport(entity.getTenantId(), entity.getId(), entity, null); broadcastEntityChangeToTransport(entity.getTenantId(), entity.getId(), entity, null);
if (old != null) {
var msg = ComponentLifecycleMsg.builder()
.tenantId(entity.getTenantId())
.entityId(entity.getId())
.profileId(entity.getDeviceProfileId())
.name(entity.getName());
if (created) {
msg.event(ComponentLifecycleEvent.CREATED);
} else {
boolean deviceNameChanged = !entity.getName().equals(old.getName()); boolean deviceNameChanged = !entity.getName().equals(old.getName());
if (deviceNameChanged) { if (deviceNameChanged) {
gatewayNotificationsService.onDeviceUpdated(entity, old); gatewayNotificationsService.onDeviceUpdated(entity, old);
} }
boolean deviceProfileChanged = !entity.getDeviceProfileId().equals(old.getDeviceProfileId()); boolean deviceProfileChanged = !entity.getDeviceProfileId().equals(old.getDeviceProfileId());
if (deviceProfileChanged) {
ComponentLifecycleMsg msg = ComponentLifecycleMsg.builder()
.tenantId(entity.getTenantId())
.entityId(entity.getId())
.event(ComponentLifecycleEvent.UPDATED)
.oldProfileId(old.getDeviceProfileId())
.profileId(entity.getDeviceProfileId())
.oldName(old.getName())
.name(entity.getName())
.build();
broadcastToCalculatedFields(ToCalculatedFieldNotificationMsg.newBuilder().setComponentLifecycleMsg(toProto(msg)).build(), TbQueueCallback.EMPTY);
}
if (deviceNameChanged || deviceProfileChanged) { if (deviceNameChanged || deviceProfileChanged) {
pushMsgToCore(new DeviceNameOrTypeUpdateMsg(entity.getTenantId(), entity.getId(), entity.getName(), entity.getType()), null); pushMsgToCore(new DeviceNameOrTypeUpdateMsg(entity.getTenantId(), entity.getId(), entity.getName(), entity.getType()), null);
} }
} else { msg.event(ComponentLifecycleEvent.UPDATED)
ComponentLifecycleMsg msg = ComponentLifecycleMsg.builder() .oldProfileId(old.getDeviceProfileId())
.tenantId(entity.getTenantId()) .oldName(old.getName());
.entityId(entity.getId())
.event(ComponentLifecycleEvent.CREATED)
.profileId(entity.getDeviceProfileId())
.name(entity.getName())
.build();
broadcastToCalculatedFields(ToCalculatedFieldNotificationMsg.newBuilder().setComponentLifecycleMsg(toProto(msg)).build(), TbQueueCallback.EMPTY);
} }
broadcastEntityStateChangeEvent(entity.getTenantId(), entity.getId(), created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); broadcast(msg.build());
sendDeviceStateServiceEvent(entity.getTenantId(), entity.getId(), created, !created, false); sendDeviceStateServiceEvent(entity.getTenantId(), entity.getId(), created, !created, false);
otaPackageStateService.update(entity, old); otaPackageStateService.update(entity, old);
} }
@ -697,48 +673,29 @@ public class DefaultTbClusterService implements TbClusterService {
@Override @Override
public void onAssetUpdated(Asset entity, Asset old) { public void onAssetUpdated(Asset entity, Asset old) {
var created = old == null; var created = old == null;
if (old != null) { var msg = ComponentLifecycleMsg.builder()
boolean assetTypeChanged = !entity.getAssetProfileId().equals(old.getAssetProfileId()); .tenantId(entity.getTenantId())
if (assetTypeChanged) { .entityId(entity.getId())
ComponentLifecycleMsg msg = ComponentLifecycleMsg.builder() .profileId(entity.getAssetProfileId())
.tenantId(entity.getTenantId()) .name(entity.getName());
.entityId(entity.getId()) if (created) {
.event(ComponentLifecycleEvent.UPDATED) msg.event(ComponentLifecycleEvent.CREATED);
.oldProfileId(old.getAssetProfileId())
.profileId(entity.getAssetProfileId())
.oldName(old.getName())
.name(entity.getName())
.build();
broadcastToCalculatedFields(ToCalculatedFieldNotificationMsg.newBuilder().setComponentLifecycleMsg(toProto(msg)).build(), TbQueueCallback.EMPTY);
}
} else { } else {
ComponentLifecycleMsg msg = ComponentLifecycleMsg.builder() msg.event(ComponentLifecycleEvent.UPDATED)
.tenantId(entity.getTenantId()) .oldProfileId(old.getAssetProfileId())
.entityId(entity.getId()) .oldName(old.getName());
.event(ComponentLifecycleEvent.CREATED)
.profileId(entity.getAssetProfileId())
.name(entity.getName())
.build();
broadcastToCalculatedFields(ToCalculatedFieldNotificationMsg.newBuilder().setComponentLifecycleMsg(toProto(msg)).build(), TbQueueCallback.EMPTY);
} }
broadcastEntityStateChangeEvent(entity.getTenantId(), entity.getId(), created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); broadcast(msg.build());
} }
@Override @Override
public void onCalculatedFieldUpdated(CalculatedField calculatedField, CalculatedField oldCalculatedField, TbQueueCallback callback) { public void onCalculatedFieldUpdated(CalculatedField calculatedField, CalculatedField oldCalculatedField, TbQueueCallback callback) {
var msg = toProto(new ComponentLifecycleMsg(calculatedField.getTenantId(), calculatedField.getId(), oldCalculatedField == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED)); broadcastEntityStateChangeEvent(calculatedField.getTenantId(), calculatedField.getId(), oldCalculatedField == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
onCalculatedFieldLifecycleMsg(msg, callback);
} }
@Override @Override
public void onCalculatedFieldDeleted(CalculatedField calculatedField, TbQueueCallback callback) { public void onCalculatedFieldDeleted(CalculatedField calculatedField, TbQueueCallback callback) {
var msg = toProto(new ComponentLifecycleMsg(calculatedField.getTenantId(), calculatedField.getId(), ComponentLifecycleEvent.DELETED)); broadcastEntityStateChangeEvent(calculatedField.getTenantId(), calculatedField.getId(), ComponentLifecycleEvent.DELETED);
onCalculatedFieldLifecycleMsg(msg, callback);
}
private void onCalculatedFieldLifecycleMsg(ComponentLifecycleMsgProto msg, TbQueueCallback callback) {
broadcastToCalculatedFields(ToCalculatedFieldNotificationMsg.newBuilder().setComponentLifecycleMsg(msg).build(), callback);
broadcastToCore(ToCoreNotificationMsg.newBuilder().setComponentLifecycle(msg).build());
} }
@Override @Override
@ -868,8 +825,4 @@ public class DefaultTbClusterService implements TbClusterService {
} }
} }
private void handleCalculatedFieldEntityDeleted(TenantId tenantId, EntityId entityId) {
ComponentLifecycleMsg msg = new ComponentLifecycleMsg(tenantId, entityId, ComponentLifecycleEvent.DELETED);
broadcastToCalculatedFields(ToCalculatedFieldNotificationMsg.newBuilder().setComponentLifecycleMsg(toProto(msg)).build(), TbQueueCallback.EMPTY);
}
} }

3
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java

@ -29,7 +29,6 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.data.rpc.RpcError; import org.thingsboard.server.common.data.rpc.RpcError;
import org.thingsboard.server.common.data.util.CollectionsUtil;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
@ -234,7 +233,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
if (event.getEvent() == ComponentLifecycleEvent.DELETED) { if (event.getEvent() == ComponentLifecycleEvent.DELETED) {
List<QueueKey> toRemove = consumers.keySet().stream() List<QueueKey> toRemove = consumers.keySet().stream()
.filter(queueKey -> queueKey.getTenantId().equals(event.getTenantId())) .filter(queueKey -> queueKey.getTenantId().equals(event.getTenantId()))
.collect(Collectors.toList()); .toList();
toRemove.forEach(queueKey -> { toRemove.forEach(queueKey -> {
removeConsumer(queueKey).ifPresent(consumer -> consumer.delete(false)); removeConsumer(queueKey).ifPresent(consumer -> consumer.delete(false));
}); });

2
application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java

@ -351,7 +351,7 @@ public class DefaultTransportApiService implements TransportApiService {
device.setAdditionalInfo(additionalInfo); device.setAdditionalInfo(additionalInfo);
device = deviceService.saveDevice(device); device = deviceService.saveDevice(device);
relationService.saveRelation(TenantId.SYS_TENANT_ID, new EntityRelation(gateway.getId(), device.getId(), "Created")); relationService.saveRelation(tenantId, new EntityRelation(gateway.getId(), device.getId(), "Created"));
TbMsgMetaData metaData = new TbMsgMetaData(); TbMsgMetaData metaData = new TbMsgMetaData();
CustomerId customerId = gateway.getCustomerId(); CustomerId customerId = gateway.getCustomerId();

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

@ -187,7 +187,9 @@ usage:
# Enable/Disable the collection of API usage statistics on a customer level # Enable/Disable the collection of API usage statistics on a customer level
enabled_per_customer: "${USAGE_STATS_REPORT_PER_CUSTOMER_ENABLED:false}" enabled_per_customer: "${USAGE_STATS_REPORT_PER_CUSTOMER_ENABLED:false}"
# Statistics reporting interval, set to send summarized data every 10 seconds by default # Statistics reporting interval, set to send summarized data every 10 seconds by default
interval: "${USAGE_STATS_REPORT_INTERVAL:10}" interval: "${USAGE_STATS_REPORT_INTERVAL:60}"
# Amount of statistic messages in pack
pack_size: "${USAGE_STATS_REPORT_PACK_SIZE:1024}"
check: check:
# Interval of checking the start of the next cycle and re-enabling the blocked tenants/customers # Interval of checking the start of the next cycle and re-enabling the blocked tenants/customers
cycle: "${USAGE_STATS_CHECK_CYCLE:60000}" cycle: "${USAGE_STATS_CHECK_CYCLE:60000}"

74
application/src/test/java/org/thingsboard/server/actors/service/ComponentActorTest.java

@ -0,0 +1,74 @@
/**
* Copyright © 2016-2025 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.actors.service;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.BDDMockito;
import org.mockito.Mockito;
import org.springframework.test.util.ReflectionTestUtils;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.msg.TbActorStopReason;
import java.util.concurrent.ScheduledFuture;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
class ComponentActorTest {
ComponentActor componentActor;
@BeforeEach
void setUp() {
componentActor = Mockito.mock(ComponentActor.class);
}
@Test
void scheduleStatsPersistTickTest() {
Assertions.assertNull(componentActor.statsScheduledFuture);
ScheduledFuture<?> statsScheduledFuture = Mockito.mock(ScheduledFuture.class);
ActorSystemContext systemContext = Mockito.mock(ActorSystemContext.class);
ReflectionTestUtils.setField(componentActor, "systemContext", systemContext);
ComponentMsgProcessor<?> processor = Mockito.mock(ComponentMsgProcessor.class);
componentActor.processor = processor;
BDDMockito.willReturn(statsScheduledFuture).given(processor).scheduleStatsPersistTick(any(), anyLong());
BDDMockito.willCallRealMethod().given(componentActor).scheduleStatsPersistTick();
componentActor.scheduleStatsPersistTick();
Assertions.assertNotNull(componentActor.statsScheduledFuture);
}
@Test
void destroyTest() {
ScheduledFuture<?> statsScheduledFuture = Mockito.mock(ScheduledFuture.class);
componentActor.statsScheduledFuture = statsScheduledFuture;
Assertions.assertNotNull(componentActor.statsScheduledFuture);
Throwable cause = new Throwable();
EntityId id = Mockito.mock(EntityId.class);
ReflectionTestUtils.setField(componentActor, "id", id);
BDDMockito.willCallRealMethod().given(componentActor).destroy(any(), any());
componentActor.destroy(TbActorStopReason.STOPPED, cause);
Mockito.verify(statsScheduledFuture).cancel(false);
Assertions.assertNull(componentActor.statsScheduledFuture);
}
}

5
application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java

@ -38,6 +38,7 @@ import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock; import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.cache.Cache; import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager; import org.springframework.cache.CacheManager;
@ -143,6 +144,7 @@ import org.thingsboard.server.dao.device.ClaimDevicesService;
import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.queue.memory.InMemoryStorage; import org.thingsboard.server.queue.memory.InMemoryStorage;
import org.thingsboard.server.service.cf.CfRocksDb;
import org.thingsboard.server.service.entitiy.tenant.profile.TbTenantProfileService; import org.thingsboard.server.service.entitiy.tenant.profile.TbTenantProfileService;
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRequest; import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRequest;
import org.thingsboard.server.service.security.auth.rest.LoginRequest; import org.thingsboard.server.service.security.auth.rest.LoginRequest;
@ -276,6 +278,9 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
@Autowired @Autowired
protected InMemoryStorage storage; protected InMemoryStorage storage;
@MockBean
protected CfRocksDb cfRocksDb;
@Rule @Rule
public TestRule watcher = new TestWatcher() { public TestRule watcher = new TestWatcher() {
protected void starting(Description description) { protected void starting(Description description) {

2
application/src/test/java/org/thingsboard/server/controller/EdqsEntityQueryControllerTest.java

@ -35,7 +35,7 @@ import static org.awaitility.Awaitility.await;
@DaoSqlTest @DaoSqlTest
@TestPropertySource(properties = { @TestPropertySource(properties = {
// "queue.type=kafka", // uncomment to use Kafka // "queue.type=kafka", // uncomment to use Kafka
// "queue.kafka.bootstrap.servers=10.7.1.254:9092", // "queue.kafka.bootstrap.servers=10.7.2.107:9092",
"queue.edqs.sync.enabled=true", "queue.edqs.sync.enabled=true",
"queue.edqs.api.supported=true", "queue.edqs.api.supported=true",
"queue.edqs.api.auto_enable=true", "queue.edqs.api.auto_enable=true",

50
application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java

@ -664,34 +664,38 @@ public class TenantControllerTest extends AbstractControllerTest {
savedDifferentTenant.setTenantProfileId(tenantProfile.getId()); savedDifferentTenant.setTenantProfileId(tenantProfile.getId());
savedDifferentTenant = saveTenant(savedDifferentTenant); savedDifferentTenant = saveTenant(savedDifferentTenant);
TenantId tenantId = differentTenantId; TenantId tenantId = differentTenantId;
await().atMost(TIMEOUT, TimeUnit.SECONDS) List<TopicPartitionInfo> isolatedTpis = await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> {
.until(() -> { List<TopicPartitionInfo> newTpis = new ArrayList<>();
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, MAIN_QUEUE_NAME, tenantId, tenantId); newTpis.add(partitionService.resolve(ServiceType.TB_RULE_ENGINE, MAIN_QUEUE_NAME, tenantId, tenantId));
return !tpi.getTenantId().get().isSysTenantId(); newTpis.add(partitionService.resolve(ServiceType.TB_RULE_ENGINE, DataConstants.CF_QUEUE_NAME, tenantId, tenantId));
}); return newTpis;
TopicPartitionInfo tpi = new TopicPartitionInfo(MAIN_QUEUE_TOPIC, tenantId, 0, false); }, newTpis -> newTpis.stream().allMatch(newTpi -> newTpi.getTenantId().get().equals(tenantId)));
String isolatedTopic = tpi.getFullTopicName(); TbMsg expectedMsg = publishTbMsg(tenantId, isolatedTpis.get(0));
TbMsg expectedMsg = publishTbMsg(tenantId, tpi);
awaitTbMsg(tbMsg -> tbMsg.getId().equals(expectedMsg.getId()), 10000); // to wait for consumer start awaitTbMsg(tbMsg -> tbMsg.getId().equals(expectedMsg.getId()), 10000); // to wait for consumer start
loginSysAdmin(); loginSysAdmin();
tenantProfile.setIsolatedTbRuleEngine(false); tenantProfile.setIsolatedTbRuleEngine(false);
tenantProfile.getProfileData().setQueueConfiguration(Collections.emptyList()); tenantProfile.getProfileData().setQueueConfiguration(Collections.emptyList());
tenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class); tenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
await().atMost(TIMEOUT, TimeUnit.SECONDS) await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> {
.until(() -> partitionService.resolve(ServiceType.TB_RULE_ENGINE, MAIN_QUEUE_NAME, tenantId, tenantId) TopicPartitionInfo newTpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, MAIN_QUEUE_NAME, tenantId, tenantId);
.getTenantId().get().isSysTenantId()); assertThat(newTpi.getTenantId()).hasValue(TenantId.SYS_TENANT_ID);
newTpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, DataConstants.CF_QUEUE_NAME, tenantId, tenantId);
assertThat(newTpi.getTenantId()).hasValue(TenantId.SYS_TENANT_ID);
});
List<UUID> submittedMsgs = new ArrayList<>(); List<UUID> submittedMsgs = new ArrayList<>();
long timeLeft = TimeUnit.SECONDS.toMillis(7); // based on topic-deletion-delay long timeLeft = TimeUnit.SECONDS.toMillis(7); // based on topic-deletion-delay
int msgs = 100; int msgs = 100;
for (int i = 1; i <= msgs; i++) { for (int i = 1; i <= msgs; i++) {
TbMsg tbMsg = publishTbMsg(tenantId, tpi); TbMsg tbMsg = publishTbMsg(tenantId, isolatedTpis.get(0));
submittedMsgs.add(tbMsg.getId()); submittedMsgs.add(tbMsg.getId());
Thread.sleep(timeLeft / msgs); Thread.sleep(timeLeft / msgs);
} }
await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> { await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> {
verify(queueAdmin, times(1)).deleteTopic(eq(isolatedTopic)); TopicPartitionInfo tpi = isolatedTpis.get(0);
// we only expect deletion of Rule Engine topic. for CF - the topic is left as is because queue draining is not supported
verify(queueAdmin, times(1)).deleteTopic(eq(tpi.getFullTopicName()));
}); });
await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> { await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
@ -719,12 +723,16 @@ public class TenantControllerTest extends AbstractControllerTest {
savedDifferentTenant.setTenantProfileId(tenantProfile.getId()); savedDifferentTenant.setTenantProfileId(tenantProfile.getId());
savedDifferentTenant = saveTenant(savedDifferentTenant); savedDifferentTenant = saveTenant(savedDifferentTenant);
TenantId tenantId = differentTenantId; TenantId tenantId = differentTenantId;
await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> { List<TopicPartitionInfo> isolatedTpis = await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> {
assertThat(partitionService.getMyPartitions(new QueueKey(ServiceType.TB_RULE_ENGINE, tenantId))).isNotNull(); List<TopicPartitionInfo> newTpis = new ArrayList<>();
}); newTpis.add(partitionService.resolve(ServiceType.TB_RULE_ENGINE, MAIN_QUEUE_NAME, tenantId, tenantId));
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, tenantId); newTpis.add(partitionService.resolve(ServiceType.TB_RULE_ENGINE, DataConstants.CF_QUEUE_NAME, tenantId, tenantId));
assertThat(tpi.getTenantId()).hasValue(tenantId); return newTpis;
TbMsg tbMsg = publishTbMsg(tenantId, tpi); }, newTpis -> newTpis.stream().allMatch(newTpi -> {
return newTpi.getTenantId().get().equals(tenantId) &&
newTpi.isMyPartition();
}));
TbMsg tbMsg = publishTbMsg(tenantId, isolatedTpis.get(0));
await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> { await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> {
verify(actorContext).tell(argThat(msg -> { verify(actorContext).tell(argThat(msg -> {
return msg instanceof QueueToRuleEngineMsg && ((QueueToRuleEngineMsg) msg).getMsg().getId().equals(tbMsg.getId()); return msg instanceof QueueToRuleEngineMsg && ((QueueToRuleEngineMsg) msg).getMsg().getId().equals(tbMsg.getId());
@ -738,7 +746,9 @@ public class TenantControllerTest extends AbstractControllerTest {
assertThatThrownBy(() -> partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, tenantId)) assertThatThrownBy(() -> partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, tenantId))
.isInstanceOf(TenantNotFoundException.class); .isInstanceOf(TenantNotFoundException.class);
verify(queueAdmin).deleteTopic(eq(tpi.getFullTopicName())); isolatedTpis.forEach(tpi -> {
verify(queueAdmin).deleteTopic(eq(tpi.getFullTopicName()));
});
}); });
} }

10
application/src/test/java/org/thingsboard/server/edge/AssetProfileEdgeTest.java

@ -121,14 +121,14 @@ public class AssetProfileEdgeTest extends AbstractEdgeTest {
Assert.assertNull(assetProfile.getDefaultRuleChainId()); Assert.assertNull(assetProfile.getDefaultRuleChainId());
Assert.assertEquals(edgeRuleChainId, assetProfile.getDefaultEdgeRuleChainId()); Assert.assertEquals(edgeRuleChainId, assetProfile.getDefaultEdgeRuleChainId());
// delete profile // delete profile and delete relation messages
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(2);
doDelete("/api/assetProfile/" + assetProfile.getUuidId()) doDelete("/api/assetProfile/" + assetProfile.getUuidId())
.andExpect(status().isOk()); .andExpect(status().isOk());
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); Optional<AssetProfileUpdateMsg> assetDeleteMsgOpt = edgeImitator.findMessageByType(AssetProfileUpdateMsg.class);
Assert.assertTrue(latestMessage instanceof AssetProfileUpdateMsg); Assert.assertTrue(assetDeleteMsgOpt.isPresent());
AssetProfileUpdateMsg assetProfileUpdateMsg = (AssetProfileUpdateMsg) latestMessage; AssetProfileUpdateMsg assetProfileUpdateMsg = assetDeleteMsgOpt.get();
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, assetProfileUpdateMsg.getMsgType()); Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, assetProfileUpdateMsg.getMsgType());
Assert.assertEquals(assetProfile.getUuidId().getMostSignificantBits(), assetProfileUpdateMsg.getIdMSB()); Assert.assertEquals(assetProfile.getUuidId().getMostSignificantBits(), assetProfileUpdateMsg.getIdMSB());
Assert.assertEquals(assetProfile.getUuidId().getLeastSignificantBits(), assetProfileUpdateMsg.getIdLSB()); Assert.assertEquals(assetProfile.getUuidId().getLeastSignificantBits(), assetProfileUpdateMsg.getIdLSB());

10
application/src/test/java/org/thingsboard/server/edge/DeviceProfileEdgeTest.java

@ -327,14 +327,14 @@ public class DeviceProfileEdgeTest extends AbstractEdgeTest {
Assert.assertNotNull(deviceProfile); Assert.assertNotNull(deviceProfile);
Assert.assertEquals("Device Profile On Edge", deviceProfile.getName()); Assert.assertEquals("Device Profile On Edge", deviceProfile.getName());
// delete profile // delete profile and delete relation messages
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(2);
doDelete("/api/deviceProfile/" + deviceProfile.getUuidId()) doDelete("/api/deviceProfile/" + deviceProfile.getUuidId())
.andExpect(status().isOk()); .andExpect(status().isOk());
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); Optional<DeviceProfileUpdateMsg> deviceDeleteMsgOpt = edgeImitator.findMessageByType(DeviceProfileUpdateMsg.class);
Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg); Assert.assertTrue(deviceDeleteMsgOpt.isPresent());
DeviceProfileUpdateMsg deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage; DeviceProfileUpdateMsg deviceProfileUpdateMsg = deviceDeleteMsgOpt.get();
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType()); Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType());
Assert.assertEquals(deviceProfile.getUuidId().getMostSignificantBits(), deviceProfileUpdateMsg.getIdMSB()); Assert.assertEquals(deviceProfile.getUuidId().getMostSignificantBits(), deviceProfileUpdateMsg.getIdMSB());
Assert.assertEquals(deviceProfile.getUuidId().getLeastSignificantBits(), deviceProfileUpdateMsg.getIdLSB()); Assert.assertEquals(deviceProfile.getUuidId().getLeastSignificantBits(), deviceProfileUpdateMsg.getIdLSB());

4
application/src/test/java/org/thingsboard/server/service/entitiy/EntityServiceTest.java

@ -282,6 +282,10 @@ public class EntityServiceTest extends AbstractControllerTest {
data.forEach(entityData -> Assert.assertNotNull(entityData.getLatest().get(EntityKeyType.ENTITY_FIELD).get("phone"))); data.forEach(entityData -> Assert.assertNotNull(entityData.getLatest().get(EntityKeyType.ENTITY_FIELD).get("phone")));
countByQueryAndCheck(query, 5); countByQueryAndCheck(query, 5);
// delete user
userService.deleteUser(tenantId, users.get(0));
countByQueryAndCheck(query, 4);
} }
private void createTestUserRelations(TenantId tenantId, List<User> users) { private void createTestUserRelations(TenantId tenantId, List<User> users) {

2
common/cluster-api/src/main/java/org/thingsboard/server/cluster/TbClusterService.java

@ -85,8 +85,6 @@ public interface TbClusterService extends TbQueueClusterService {
void pushMsgToCalculatedFields(TopicPartitionInfo tpi, UUID msgId, ToCalculatedFieldMsg msg, TbQueueCallback callback); void pushMsgToCalculatedFields(TopicPartitionInfo tpi, UUID msgId, ToCalculatedFieldMsg msg, TbQueueCallback callback);
void pushNotificationToCalculatedFields(TenantId tenantId, EntityId entityId, TransportProtos.ToCalculatedFieldNotificationMsg msg, TbQueueCallback callback);
void broadcastEntityStateChangeEvent(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent state); void broadcastEntityStateChangeEvent(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent state);
void onDeviceProfileChange(DeviceProfile deviceProfile, DeviceProfile oldDeviceProfile, TbQueueCallback callback); void onDeviceProfileChange(DeviceProfile deviceProfile, DeviceProfile oldDeviceProfile, TbQueueCallback callback);

11
common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java

@ -86,4 +86,15 @@ public enum EntityType {
this.tableName = tableName; this.tableName = tableName;
} }
public boolean isOneOf(EntityType... types) {
if (types == null) {
return false;
}
for (EntityType type : types) {
if (this == type) {
return true;
}
}
return false;
}
} }

1
common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/EntityFields.java

@ -148,6 +148,7 @@ public interface EntityFields {
default String getAsString(String key) { default String getAsString(String key) {
return switch (key) { return switch (key) {
case "createdTime" -> Long.toString(getCreatedTime()); case "createdTime" -> Long.toString(getCreatedTime());
case "title" -> getName();
case "type" -> getType(); case "type" -> getType();
case "label" -> getLabel(); case "label" -> getLabel();
case "additionalInfo" -> getAdditionalInfo(); case "additionalInfo" -> getAdditionalInfo();

1
common/edqs/src/main/java/org/thingsboard/server/edqs/processor/EdqsProcessor.java

@ -141,6 +141,7 @@ public class EdqsProcessor implements TbQueueHandler<TbProtoQueueMsg<ToEdqsMsg>,
consumer.commit(); consumer.commit();
}) })
.consumerCreator((config, partitionId) -> queueFactory.createEdqsMsgConsumer(EdqsQueue.EVENTS)) .consumerCreator((config, partitionId) -> queueFactory.createEdqsMsgConsumer(EdqsQueue.EVENTS))
.queueAdmin(queueFactory.getEdqsQueueAdmin())
.consumerExecutor(consumersExecutor) .consumerExecutor(consumersExecutor)
.taskExecutor(taskExecutor) .taskExecutor(taskExecutor)
.scheduler(scheduler) .scheduler(scheduler)

11
common/edqs/src/main/java/org/thingsboard/server/edqs/state/KafkaEdqsStateService.java

@ -33,7 +33,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToEdqsMsg;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.common.consumer.PartitionedQueueConsumerManager; import org.thingsboard.server.queue.common.consumer.PartitionedQueueConsumerManager;
import org.thingsboard.server.queue.common.consumer.QueueConsumerManager; import org.thingsboard.server.queue.common.consumer.QueueConsumerManager;
import org.thingsboard.server.queue.common.consumer.QueueStateService; import org.thingsboard.server.queue.common.state.KafkaQueueStateService;
import org.thingsboard.server.queue.common.state.QueueStateService;
import org.thingsboard.server.queue.discovery.QueueKey; import org.thingsboard.server.queue.discovery.QueueKey;
import org.thingsboard.server.queue.discovery.TopicService; import org.thingsboard.server.queue.discovery.TopicService;
import org.thingsboard.server.queue.edqs.EdqsConfig; import org.thingsboard.server.queue.edqs.EdqsConfig;
@ -89,13 +90,13 @@ public class KafkaEdqsStateService implements EdqsStateService {
consumer.commit(); consumer.commit();
}) })
.consumerCreator((config, partitionId) -> queueFactory.createEdqsMsgConsumer(EdqsQueue.STATE)) .consumerCreator((config, partitionId) -> queueFactory.createEdqsMsgConsumer(EdqsQueue.STATE))
.queueAdmin(queueFactory.getEdqsQueueAdmin())
.consumerExecutor(eventConsumer.getConsumerExecutor()) .consumerExecutor(eventConsumer.getConsumerExecutor())
.taskExecutor(eventConsumer.getTaskExecutor()) .taskExecutor(eventConsumer.getTaskExecutor())
.scheduler(eventConsumer.getScheduler()) .scheduler(eventConsumer.getScheduler())
.uncaughtErrorHandler(edqsProcessor.getErrorHandler()) .uncaughtErrorHandler(edqsProcessor.getErrorHandler())
.build(); .build();
queueStateService = new QueueStateService<>(); queueStateService = new KafkaQueueStateService<>(eventConsumer, stateConsumer);
queueStateService.init(stateConsumer, eventConsumer);
eventsToBackupConsumer = QueueConsumerManager.<TbProtoQueueMsg<ToEdqsMsg>>builder() eventsToBackupConsumer = QueueConsumerManager.<TbProtoQueueMsg<ToEdqsMsg>>builder()
.name("edqs-events-to-backup-consumer") .name("edqs-events-to-backup-consumer")
@ -149,11 +150,11 @@ public class KafkaEdqsStateService implements EdqsStateService {
@Override @Override
public void process(Set<TopicPartitionInfo> partitions) { public void process(Set<TopicPartitionInfo> partitions) {
if (queueStateService.getPartitions() == null) { if (queueStateService.getPartitions().isEmpty()) {
eventsToBackupConsumer.subscribe(); eventsToBackupConsumer.subscribe();
eventsToBackupConsumer.launch(); eventsToBackupConsumer.launch();
} }
queueStateService.update(partitions); queueStateService.update(new QueueKey(ServiceType.EDQS), partitions);
} }
@Override @Override

3
common/message/src/main/java/org/thingsboard/server/common/msg/cf/CalculatedFieldEntityLifecycleMsg.java

@ -16,19 +16,16 @@
package org.thingsboard.server.common.msg.cf; package org.thingsboard.server.common.msg.cf;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.MsgType; import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg; import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.TbCallback;
@Data @Data
public class CalculatedFieldEntityLifecycleMsg implements ToCalculatedFieldSystemMsg { public class CalculatedFieldEntityLifecycleMsg implements ToCalculatedFieldSystemMsg {
private final TenantId tenantId; private final TenantId tenantId;
private final ComponentLifecycleMsg data; private final ComponentLifecycleMsg data;
private final TbCallback callback;
@Override @Override
public MsgType getMsgType() { public MsgType getMsgType() {

35
common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java

@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.ApiUsageStateValue;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
@ -93,6 +94,7 @@ import org.thingsboard.server.common.msg.rule.engine.DeviceEdgeUpdateMsg;
import org.thingsboard.server.common.msg.rule.engine.DeviceNameOrTypeUpdateMsg; import org.thingsboard.server.common.msg.rule.engine.DeviceNameOrTypeUpdateMsg;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto; import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto;
import org.thingsboard.server.gen.transport.TransportProtos.ApiUsageRecordKeyProto;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -432,6 +434,39 @@ public class ProtoUtils {
return builder.build(); return builder.build();
} }
public static ApiUsageRecordKeyProto toProto(ApiUsageRecordKey apiUsageRecordKey) {
return switch (apiUsageRecordKey) {
case TRANSPORT_MSG_COUNT -> ApiUsageRecordKeyProto.TRANSPORT_MSG_COUNT;
case TRANSPORT_DP_COUNT -> ApiUsageRecordKeyProto.TRANSPORT_DP_COUNT;
case STORAGE_DP_COUNT -> ApiUsageRecordKeyProto.STORAGE_DP_COUNT;
case RE_EXEC_COUNT -> ApiUsageRecordKeyProto.RE_EXEC_COUNT;
case JS_EXEC_COUNT -> ApiUsageRecordKeyProto.JS_EXEC_COUNT;
case TBEL_EXEC_COUNT -> ApiUsageRecordKeyProto.TBEL_EXEC_COUNT;
case EMAIL_EXEC_COUNT -> ApiUsageRecordKeyProto.EMAIL_EXEC_COUNT;
case SMS_EXEC_COUNT -> ApiUsageRecordKeyProto.SMS_EXEC_COUNT;
case CREATED_ALARMS_COUNT -> ApiUsageRecordKeyProto.CREATED_ALARMS_COUNT;
case ACTIVE_DEVICES -> ApiUsageRecordKeyProto.ACTIVE_DEVICES;
case INACTIVE_DEVICES -> ApiUsageRecordKeyProto.INACTIVE_DEVICES;
};
}
public static ApiUsageRecordKey fromProto(ApiUsageRecordKeyProto proto) {
return switch (proto) {
case UNRECOGNIZED -> null;
case TRANSPORT_MSG_COUNT -> ApiUsageRecordKey.TRANSPORT_MSG_COUNT;
case TRANSPORT_DP_COUNT -> ApiUsageRecordKey.TRANSPORT_DP_COUNT;
case STORAGE_DP_COUNT -> ApiUsageRecordKey.STORAGE_DP_COUNT;
case RE_EXEC_COUNT -> ApiUsageRecordKey.RE_EXEC_COUNT;
case JS_EXEC_COUNT -> ApiUsageRecordKey.JS_EXEC_COUNT;
case TBEL_EXEC_COUNT -> ApiUsageRecordKey.TBEL_EXEC_COUNT;
case EMAIL_EXEC_COUNT -> ApiUsageRecordKey.EMAIL_EXEC_COUNT;
case SMS_EXEC_COUNT -> ApiUsageRecordKey.SMS_EXEC_COUNT;
case CREATED_ALARMS_COUNT -> ApiUsageRecordKey.CREATED_ALARMS_COUNT;
case ACTIVE_DEVICES -> ApiUsageRecordKey.ACTIVE_DEVICES;
case INACTIVE_DEVICES -> ApiUsageRecordKey.INACTIVE_DEVICES;
};
}
private static ToDeviceActorNotificationMsg fromProto(TransportProtos.DeviceAttributesEventMsgProto proto) { private static ToDeviceActorNotificationMsg fromProto(TransportProtos.DeviceAttributesEventMsgProto proto) {
return new DeviceAttributesEventNotificationMsg( return new DeviceAttributesEventNotificationMsg(
TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())), TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())),

40
common/proto/src/main/proto/queue.proto

@ -63,6 +63,20 @@ enum EntityTypeProto {
CALCULATED_FIELD_LINK = 40; CALCULATED_FIELD_LINK = 40;
} }
enum ApiUsageRecordKeyProto {
TRANSPORT_MSG_COUNT = 0;
TRANSPORT_DP_COUNT = 1;
STORAGE_DP_COUNT = 2;
RE_EXEC_COUNT = 3;
JS_EXEC_COUNT = 4;
TBEL_EXEC_COUNT = 5;
EMAIL_EXEC_COUNT = 6;
SMS_EXEC_COUNT = 7;
CREATED_ALARMS_COUNT = 8;
ACTIVE_DEVICES = 9;
INACTIVE_DEVICES = 10;
}
/** /**
* Service Discovery Data Structures; * Service Discovery Data Structures;
*/ */
@ -1674,14 +1688,12 @@ message ToEdgeEventNotificationMsg {
} }
message ToCalculatedFieldMsg { message ToCalculatedFieldMsg {
ComponentLifecycleMsgProto componentLifecycleMsg = 1; CalculatedFieldTelemetryMsgProto telemetryMsg = 1;
CalculatedFieldTelemetryMsgProto telemetryMsg = 2; CalculatedFieldLinkedTelemetryMsgProto linkedTelemetryMsg = 2;
CalculatedFieldLinkedTelemetryMsgProto linkedTelemetryMsg = 3;
} }
message ToCalculatedFieldNotificationMsg { message ToCalculatedFieldNotificationMsg {
ComponentLifecycleMsgProto componentLifecycleMsg = 1; CalculatedFieldLinkedTelemetryMsgProto linkedTelemetryMsg = 1;
CalculatedFieldLinkedTelemetryMsgProto linkedTelemetryMsg = 2;
} }
/* Messages that are handled by ThingsBoard RuleEngine Service */ /* Messages that are handled by ThingsBoard RuleEngine Service */
@ -1720,12 +1732,25 @@ message ToTransportMsg {
repeated QueueDeleteMsg queueDeleteMsgs = 16; repeated QueueDeleteMsg queueDeleteMsgs = 16;
} }
message UsageStatsKVProto{ message UsageStatsKVProto {
string key = 1; string key = 1 [deprecated=true];
int64 value = 2; int64 value = 2;
ApiUsageRecordKeyProto recordKey = 3;
} }
message ToUsageStatsServiceMsg { message ToUsageStatsServiceMsg {
int64 tenantIdMSB = 1 [deprecated=true];
int64 tenantIdLSB = 2 [deprecated=true];
int64 entityIdMSB = 3 [deprecated=true];
int64 entityIdLSB = 4 [deprecated=true];
repeated UsageStatsKVProto values = 5 [deprecated=true];
int64 customerIdMSB = 6 [deprecated=true];
int64 customerIdLSB = 7 [deprecated=true];
string serviceId = 8;
repeated UsageStatsServiceMsg msgs = 9;
}
message UsageStatsServiceMsg {
int64 tenantIdMSB = 1; int64 tenantIdMSB = 1;
int64 tenantIdLSB = 2; int64 tenantIdLSB = 2;
int64 entityIdMSB = 3; int64 entityIdMSB = 3;
@ -1733,7 +1758,6 @@ message ToUsageStatsServiceMsg {
repeated UsageStatsKVProto values = 5; repeated UsageStatsKVProto values = 5;
int64 customerIdMSB = 6; int64 customerIdMSB = 6;
int64 customerIdLSB = 7; int64 customerIdLSB = 7;
string serviceId = 8;
} }
message ToOtaPackageStateServiceMsg { message ToOtaPackageStateServiceMsg {

21
common/queue/src/main/java/org/thingsboard/server/queue/common/consumer/PartitionedQueueConsumerManager.java

@ -20,9 +20,11 @@ import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.queue.QueueConfig; import org.thingsboard.server.common.data.queue.QueueConfig;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueMsg; import org.thingsboard.server.queue.TbQueueMsg;
import org.thingsboard.server.queue.common.consumer.TbQueueConsumerManagerTask.AddPartitionsTask; import org.thingsboard.server.queue.common.consumer.TbQueueConsumerManagerTask.AddPartitionsTask;
import org.thingsboard.server.queue.common.consumer.TbQueueConsumerManagerTask.DeletePartitionsTask;
import org.thingsboard.server.queue.common.consumer.TbQueueConsumerManagerTask.RemovePartitionsTask; import org.thingsboard.server.queue.common.consumer.TbQueueConsumerManagerTask.RemovePartitionsTask;
import org.thingsboard.server.queue.discovery.QueueKey; import org.thingsboard.server.queue.discovery.QueueKey;
@ -36,17 +38,19 @@ import java.util.function.Consumer;
public class PartitionedQueueConsumerManager<M extends TbQueueMsg> extends MainQueueConsumerManager<M, QueueConfig> { public class PartitionedQueueConsumerManager<M extends TbQueueMsg> extends MainQueueConsumerManager<M, QueueConfig> {
private final ConsumerPerPartitionWrapper consumerWrapper; private final ConsumerPerPartitionWrapper consumerWrapper;
private final TbQueueAdmin queueAdmin;
@Getter @Getter
private final String topic; private final String topic;
@Builder(builderMethodName = "create") // not to conflict with super.builder() @Builder(builderMethodName = "create") // not to conflict with super.builder()
public PartitionedQueueConsumerManager(QueueKey queueKey, String topic, long pollInterval, MsgPackProcessor<M, QueueConfig> msgPackProcessor, public PartitionedQueueConsumerManager(QueueKey queueKey, String topic, long pollInterval, MsgPackProcessor<M, QueueConfig> msgPackProcessor,
BiFunction<QueueConfig, Integer, TbQueueConsumer<M>> consumerCreator, BiFunction<QueueConfig, Integer, TbQueueConsumer<M>> consumerCreator, TbQueueAdmin queueAdmin,
ExecutorService consumerExecutor, ScheduledExecutorService scheduler, ExecutorService consumerExecutor, ScheduledExecutorService scheduler,
ExecutorService taskExecutor, Consumer<Throwable> uncaughtErrorHandler) { ExecutorService taskExecutor, Consumer<Throwable> uncaughtErrorHandler) {
super(queueKey, QueueConfig.of(true, pollInterval), msgPackProcessor, consumerCreator, consumerExecutor, scheduler, taskExecutor, uncaughtErrorHandler); super(queueKey, QueueConfig.of(true, pollInterval), msgPackProcessor, consumerCreator, consumerExecutor, scheduler, taskExecutor, uncaughtErrorHandler);
this.topic = topic; this.topic = topic;
this.consumerWrapper = (ConsumerPerPartitionWrapper) super.consumerWrapper; this.consumerWrapper = (ConsumerPerPartitionWrapper) super.consumerWrapper;
this.queueAdmin = queueAdmin;
} }
@Override @Override
@ -57,6 +61,17 @@ public class PartitionedQueueConsumerManager<M extends TbQueueMsg> extends MainQ
} else if (task instanceof RemovePartitionsTask removePartitionsTask) { } else if (task instanceof RemovePartitionsTask removePartitionsTask) {
log.info("[{}] Removed partitions: {}", queueKey, removePartitionsTask.partitions()); log.info("[{}] Removed partitions: {}", queueKey, removePartitionsTask.partitions());
consumerWrapper.removePartitions(removePartitionsTask.partitions()); consumerWrapper.removePartitions(removePartitionsTask.partitions());
} else if (task instanceof DeletePartitionsTask deletePartitionsTask) {
log.info("[{}] Removing partitions and deleting topics: {}", queueKey, deletePartitionsTask.partitions());
consumerWrapper.removePartitions(deletePartitionsTask.partitions());
deletePartitionsTask.partitions().forEach(tpi -> {
String topic = tpi.getFullTopicName();
try {
queueAdmin.deleteTopic(topic);
} catch (Throwable t) {
log.error("Failed to delete topic {}", topic, t);
}
});
} }
} }
@ -72,4 +87,8 @@ public class PartitionedQueueConsumerManager<M extends TbQueueMsg> extends MainQ
addTask(new RemovePartitionsTask(partitions)); addTask(new RemovePartitionsTask(partitions));
} }
public void delete(Set<TopicPartitionInfo> partitions) {
addTask(new DeletePartitionsTask(partitions));
}
} }

98
common/queue/src/main/java/org/thingsboard/server/queue/common/consumer/QueueStateService.java

@ -1,98 +0,0 @@
/**
* Copyright © 2016-2025 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.queue.common.consumer;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.queue.TbQueueMsg;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static org.thingsboard.server.common.msg.queue.TopicPartitionInfo.withTopic;
@Slf4j
public class QueueStateService<E extends TbQueueMsg, S extends TbQueueMsg> {
private PartitionedQueueConsumerManager<S> stateConsumer;
private PartitionedQueueConsumerManager<E> eventConsumer;
@Getter
private Set<TopicPartitionInfo> partitions;
private final Set<TopicPartitionInfo> partitionsInProgress = ConcurrentHashMap.newKeySet();
private boolean initialized;
private final ReadWriteLock partitionsLock = new ReentrantReadWriteLock();
public void init(PartitionedQueueConsumerManager<S> stateConsumer, PartitionedQueueConsumerManager<E> eventConsumer) {
this.stateConsumer = stateConsumer;
this.eventConsumer = eventConsumer;
}
public void update(Set<TopicPartitionInfo> newPartitions) {
newPartitions = withTopic(newPartitions, stateConsumer.getTopic());
var writeLock = partitionsLock.writeLock();
writeLock.lock();
Set<TopicPartitionInfo> oldPartitions = this.partitions != null ? this.partitions : Collections.emptySet();
Set<TopicPartitionInfo> addedPartitions;
Set<TopicPartitionInfo> removedPartitions;
try {
addedPartitions = new HashSet<>(newPartitions);
addedPartitions.removeAll(oldPartitions);
removedPartitions = new HashSet<>(oldPartitions);
removedPartitions.removeAll(newPartitions);
this.partitions = newPartitions;
} finally {
writeLock.unlock();
}
if (!removedPartitions.isEmpty()) {
stateConsumer.removePartitions(removedPartitions);
eventConsumer.removePartitions(withTopic(removedPartitions, eventConsumer.getTopic()));
}
if (!addedPartitions.isEmpty()) {
partitionsInProgress.addAll(addedPartitions);
stateConsumer.addPartitions(addedPartitions, partition -> {
var readLock = partitionsLock.readLock();
readLock.lock();
try {
partitionsInProgress.remove(partition);
log.info("Finished partition {} (still in progress: {})", partition, partitionsInProgress);
if (partitionsInProgress.isEmpty()) {
log.info("All partitions processed");
}
if (this.partitions.contains(partition)) {
eventConsumer.addPartitions(Set.of(partition.withTopic(eventConsumer.getTopic())));
}
} finally {
readLock.unlock();
}
});
}
initialized = true;
}
public Set<TopicPartitionInfo> getPartitionsInProgress() {
return initialized ? partitionsInProgress : null;
}
}

2
common/queue/src/main/java/org/thingsboard/server/queue/common/consumer/QueueTaskType.java

@ -20,6 +20,6 @@ import java.io.Serializable;
public enum QueueTaskType implements Serializable { public enum QueueTaskType implements Serializable {
UPDATE_PARTITIONS, UPDATE_CONFIG, DELETE, UPDATE_PARTITIONS, UPDATE_CONFIG, DELETE,
ADD_PARTITIONS, REMOVE_PARTITIONS ADD_PARTITIONS, REMOVE_PARTITIONS, DELETE_PARTITIONS
} }

7
common/queue/src/main/java/org/thingsboard/server/queue/common/consumer/TbQueueConsumerManagerTask.java

@ -60,4 +60,11 @@ public interface TbQueueConsumerManagerTask {
} }
} }
record DeletePartitionsTask(Set<TopicPartitionInfo> partitions) implements TbQueueConsumerManagerTask {
@Override
public QueueTaskType getType() {
return QueueTaskType.REMOVE_PARTITIONS;
}
}
} }

27
common/queue/src/main/java/org/thingsboard/server/queue/common/state/DefaultQueueStateService.java

@ -0,0 +1,27 @@
/**
* Copyright © 2016-2025 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.queue.common.state;
import org.thingsboard.server.queue.TbQueueMsg;
import org.thingsboard.server.queue.common.consumer.PartitionedQueueConsumerManager;
public class DefaultQueueStateService<E extends TbQueueMsg, S extends TbQueueMsg> extends QueueStateService<E, S> {
public DefaultQueueStateService(PartitionedQueueConsumerManager<E> eventConsumer) {
super(eventConsumer);
}
}

81
common/queue/src/main/java/org/thingsboard/server/queue/common/state/KafkaQueueStateService.java

@ -0,0 +1,81 @@
/**
* Copyright © 2016-2025 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.queue.common.state;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.queue.TbQueueMsg;
import org.thingsboard.server.queue.common.consumer.PartitionedQueueConsumerManager;
import org.thingsboard.server.queue.discovery.QueueKey;
import java.util.Set;
import static org.thingsboard.server.common.msg.queue.TopicPartitionInfo.withTopic;
@Slf4j
public class KafkaQueueStateService<E extends TbQueueMsg, S extends TbQueueMsg> extends QueueStateService<E, S> {
private final PartitionedQueueConsumerManager<S> stateConsumer;
public KafkaQueueStateService(PartitionedQueueConsumerManager<E> eventConsumer, PartitionedQueueConsumerManager<S> stateConsumer) {
super(eventConsumer);
this.stateConsumer = stateConsumer;
}
@Override
protected void addPartitions(QueueKey queueKey, Set<TopicPartitionInfo> partitions) {
Set<TopicPartitionInfo> statePartitions = withTopic(partitions, stateConsumer.getTopic());
partitionsInProgress.addAll(statePartitions);
stateConsumer.addPartitions(statePartitions, statePartition -> {
var readLock = partitionsLock.readLock();
readLock.lock();
try {
partitionsInProgress.remove(statePartition);
log.info("Finished partition {} (still in progress: {})", statePartition, partitionsInProgress);
if (partitionsInProgress.isEmpty()) {
log.info("All partitions processed");
}
TopicPartitionInfo eventPartition = statePartition.withTopic(eventConsumer.getTopic());
if (this.partitions.get(queueKey).contains(eventPartition)) {
eventConsumer.addPartitions(Set.of(eventPartition));
}
} finally {
readLock.unlock();
}
});
}
@Override
protected void removePartitions(QueueKey queueKey, Set<TopicPartitionInfo> partitions) {
super.removePartitions(queueKey, partitions);
stateConsumer.removePartitions(withTopic(partitions, stateConsumer.getTopic()));
}
@Override
protected void deletePartitions(Set<TopicPartitionInfo> partitions) {
super.deletePartitions(partitions);
stateConsumer.delete(withTopic(partitions, stateConsumer.getTopic()));
}
@Override
public void stop() {
super.stop();
stateConsumer.stop();
stateConsumer.awaitStop();
}
}

114
common/queue/src/main/java/org/thingsboard/server/queue/common/state/QueueStateService.java

@ -0,0 +1,114 @@
/**
* Copyright © 2016-2025 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.queue.common.state;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.queue.TbQueueMsg;
import org.thingsboard.server.queue.common.consumer.PartitionedQueueConsumerManager;
import org.thingsboard.server.queue.discovery.QueueKey;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static org.thingsboard.server.common.msg.queue.TopicPartitionInfo.withTopic;
@Slf4j
public abstract class QueueStateService<E extends TbQueueMsg, S extends TbQueueMsg> {
protected final PartitionedQueueConsumerManager<E> eventConsumer;
@Getter
protected final Map<QueueKey, Set<TopicPartitionInfo>> partitions = new HashMap<>();
protected final Set<TopicPartitionInfo> partitionsInProgress = ConcurrentHashMap.newKeySet();
protected boolean initialized;
protected final ReadWriteLock partitionsLock = new ReentrantReadWriteLock();
protected QueueStateService(PartitionedQueueConsumerManager<E> eventConsumer) {
this.eventConsumer = eventConsumer;
}
public void update(QueueKey queueKey, Set<TopicPartitionInfo> newPartitions) {
newPartitions = withTopic(newPartitions, eventConsumer.getTopic());
var writeLock = partitionsLock.writeLock();
writeLock.lock();
Set<TopicPartitionInfo> oldPartitions = this.partitions.getOrDefault(queueKey, Collections.emptySet());
Set<TopicPartitionInfo> addedPartitions;
Set<TopicPartitionInfo> removedPartitions;
try {
addedPartitions = new HashSet<>(newPartitions);
addedPartitions.removeAll(oldPartitions);
removedPartitions = new HashSet<>(oldPartitions);
removedPartitions.removeAll(newPartitions);
this.partitions.put(queueKey, newPartitions);
} finally {
writeLock.unlock();
}
if (!removedPartitions.isEmpty()) {
removePartitions(queueKey, removedPartitions);
}
if (!addedPartitions.isEmpty()) {
addPartitions(queueKey, addedPartitions);
}
initialized = true;
}
protected void addPartitions(QueueKey queueKey, Set<TopicPartitionInfo> partitions) {
eventConsumer.addPartitions(partitions);
}
protected void removePartitions(QueueKey queueKey, Set<TopicPartitionInfo> partitions) {
eventConsumer.removePartitions(partitions);
}
public void delete(Set<TopicPartitionInfo> partitions) {
if (partitions.isEmpty()) {
return;
}
var writeLock = partitionsLock.writeLock();
writeLock.lock();
try {
this.partitions.values().forEach(tpis -> tpis.removeAll(partitions));
} finally {
writeLock.unlock();
}
deletePartitions(partitions);
}
protected void deletePartitions(Set<TopicPartitionInfo> partitions) {
eventConsumer.delete(withTopic(partitions, eventConsumer.getTopic()));
}
public Set<TopicPartitionInfo> getPartitionsInProgress() {
return initialized ? partitionsInProgress : null;
}
public void stop() {
eventConsumer.stop();
eventConsumer.awaitStop();
}
}

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

@ -52,7 +52,10 @@ import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.thingsboard.server.common.data.DataConstants.CF_QUEUE_NAME;
import static org.thingsboard.server.common.data.DataConstants.CF_STATES_QUEUE_NAME;
import static org.thingsboard.server.common.data.DataConstants.EDGE_QUEUE_NAME; import static org.thingsboard.server.common.data.DataConstants.EDGE_QUEUE_NAME;
import static org.thingsboard.server.common.data.DataConstants.MAIN_QUEUE_NAME; import static org.thingsboard.server.common.data.DataConstants.MAIN_QUEUE_NAME;
@ -159,16 +162,7 @@ public class HashPartitionService implements PartitionService {
List<QueueRoutingInfo> queueRoutingInfoList = getQueueRoutingInfos(); List<QueueRoutingInfo> queueRoutingInfoList = getQueueRoutingInfos();
queueRoutingInfoList.forEach(queue -> { queueRoutingInfoList.forEach(queue -> {
QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, queue); QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, queue);
if (DataConstants.MAIN_QUEUE_NAME.equals(queueKey.getQueueName())) { updateQueue(queueKey, queue.getQueueTopic(), queue.getPartitions());
QueueKey cfQueueKey = queueKey.withQueueName(DataConstants.CF_QUEUE_NAME);
partitionSizesMap.put(cfQueueKey, queue.getPartitions());
partitionTopicsMap.put(cfQueueKey, cfEventTopic);
QueueKey cfQueueStatesKey = queueKey.withQueueName(DataConstants.CF_STATES_QUEUE_NAME);
partitionSizesMap.put(cfQueueStatesKey, queue.getPartitions());
partitionTopicsMap.put(cfQueueStatesKey, cfStateTopic);
}
partitionTopicsMap.put(queueKey, queue.getQueueTopic());
partitionSizesMap.put(queueKey, queue.getPartitions());
queueConfigs.put(queueKey, new QueueConfig(queue)); queueConfigs.put(queueKey, new QueueConfig(queue));
}); });
} }
@ -215,16 +209,7 @@ public class HashPartitionService implements PartitionService {
QueueRoutingInfo queueRoutingInfo = new QueueRoutingInfo(queueUpdateMsg); QueueRoutingInfo queueRoutingInfo = new QueueRoutingInfo(queueUpdateMsg);
TenantId tenantId = queueRoutingInfo.getTenantId(); TenantId tenantId = queueRoutingInfo.getTenantId();
QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, queueRoutingInfo.getQueueName(), tenantId); QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, queueRoutingInfo.getQueueName(), tenantId);
if (DataConstants.MAIN_QUEUE_NAME.equals(queueKey.getQueueName())) { updateQueue(queueKey, queueRoutingInfo.getQueueTopic(), queueRoutingInfo.getPartitions());
QueueKey cfQueueKey = queueKey.withQueueName(DataConstants.CF_QUEUE_NAME);
partitionSizesMap.put(cfQueueKey, queueRoutingInfo.getPartitions());
partitionTopicsMap.put(cfQueueKey, cfEventTopic);
QueueKey cfQueueStatesKey = queueKey.withQueueName(DataConstants.CF_STATES_QUEUE_NAME);
partitionSizesMap.put(cfQueueStatesKey, queueRoutingInfo.getPartitions());
partitionTopicsMap.put(cfQueueStatesKey, cfStateTopic);
}
partitionTopicsMap.put(queueKey, queueRoutingInfo.getQueueTopic());
partitionSizesMap.put(queueKey, queueRoutingInfo.getPartitions());
queueConfigs.put(queueKey, new QueueConfig(queueRoutingInfo)); queueConfigs.put(queueKey, new QueueConfig(queueRoutingInfo));
if (!tenantId.isSysTenantId()) { if (!tenantId.isSysTenantId()) {
tenantRoutingInfoMap.remove(tenantId); tenantRoutingInfoMap.remove(tenantId);
@ -235,9 +220,15 @@ public class HashPartitionService implements PartitionService {
@Override @Override
public void removeQueues(List<TransportProtos.QueueDeleteMsg> queueDeleteMsgs) { public void removeQueues(List<TransportProtos.QueueDeleteMsg> queueDeleteMsgs) {
List<QueueKey> queueKeys = queueDeleteMsgs.stream() List<QueueKey> queueKeys = queueDeleteMsgs.stream()
.map(queueDeleteMsg -> { .flatMap(queueDeleteMsg -> {
TenantId tenantId = TenantId.fromUUID(new UUID(queueDeleteMsg.getTenantIdMSB(), queueDeleteMsg.getTenantIdLSB())); TenantId tenantId = TenantId.fromUUID(new UUID(queueDeleteMsg.getTenantIdMSB(), queueDeleteMsg.getTenantIdLSB()));
return new QueueKey(ServiceType.TB_RULE_ENGINE, queueDeleteMsg.getQueueName(), tenantId); QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, queueDeleteMsg.getQueueName(), tenantId);
if (queueKey.getQueueName().equals(MAIN_QUEUE_NAME)) {
return Stream.of(queueKey, queueKey.withQueueName(CF_QUEUE_NAME),
queueKey.withQueueName(CF_STATES_QUEUE_NAME));
} else {
return Stream.of(queueKey);
}
}).toList(); }).toList();
queueKeys.forEach(queueKey -> { queueKeys.forEach(queueKey -> {
removeQueue(queueKey); removeQueue(queueKey);
@ -252,25 +243,38 @@ public class HashPartitionService implements PartitionService {
@Override @Override
public void removeTenant(TenantId tenantId) { public void removeTenant(TenantId tenantId) {
List<QueueKey> queueKeys = partitionSizesMap.keySet().stream() List<QueueKey> queueKeys = partitionSizesMap.keySet().stream()
.filter(queueKey -> tenantId.equals(queueKey.getTenantId())).toList(); .filter(queueKey -> tenantId.equals(queueKey.getTenantId()))
.flatMap(queueKey -> {
if (queueKey.getQueueName().equals(MAIN_QUEUE_NAME)) {
return Stream.of(queueKey, queueKey.withQueueName(CF_QUEUE_NAME),
queueKey.withQueueName(CF_STATES_QUEUE_NAME));
} else {
return Stream.of(queueKey);
}
})
.toList();
queueKeys.forEach(this::removeQueue); queueKeys.forEach(this::removeQueue);
evictTenantInfo(tenantId); evictTenantInfo(tenantId);
} }
private void updateQueue(QueueKey queueKey, String topic, int partitions) {
partitionTopicsMap.put(queueKey, topic);
partitionSizesMap.put(queueKey, partitions);
if (DataConstants.MAIN_QUEUE_NAME.equals(queueKey.getQueueName())) {
QueueKey cfQueueKey = queueKey.withQueueName(DataConstants.CF_QUEUE_NAME);
partitionTopicsMap.put(cfQueueKey, cfEventTopic);
partitionSizesMap.put(cfQueueKey, partitions);
QueueKey cfStatesQueueKey = queueKey.withQueueName(DataConstants.CF_STATES_QUEUE_NAME);
partitionTopicsMap.put(cfStatesQueueKey, cfStateTopic);
partitionSizesMap.put(cfStatesQueueKey, partitions);
}
}
private void removeQueue(QueueKey queueKey) { private void removeQueue(QueueKey queueKey) {
myPartitions.remove(queueKey); myPartitions.remove(queueKey);
partitionTopicsMap.remove(queueKey); partitionTopicsMap.remove(queueKey);
partitionSizesMap.remove(queueKey); partitionSizesMap.remove(queueKey);
queueConfigs.remove(queueKey); queueConfigs.remove(queueKey);
if (DataConstants.MAIN_QUEUE_NAME.equals(queueKey.getQueueName())) {
QueueKey cfQueueKey = queueKey.withQueueName(DataConstants.CF_QUEUE_NAME);
partitionSizesMap.remove(cfQueueKey);
partitionTopicsMap.remove(cfQueueKey);
QueueKey cfQueueStatesKey = queueKey.withQueueName(DataConstants.CF_STATES_QUEUE_NAME);
partitionSizesMap.remove(cfQueueStatesKey);
partitionTopicsMap.remove(cfQueueStatesKey);
}
} }
@Override @Override

3
common/queue/src/main/java/org/thingsboard/server/queue/discovery/QueueKey.java

@ -23,9 +23,6 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import static org.thingsboard.server.common.data.DataConstants.CF_QUEUE_NAME;
import static org.thingsboard.server.common.data.DataConstants.CF_STATES_QUEUE_NAME;
@Data @Data
@AllArgsConstructor @AllArgsConstructor
public class QueueKey { public class QueueKey {

1
common/queue/src/main/java/org/thingsboard/server/queue/discovery/event/PartitionChangeEvent.java

@ -24,7 +24,6 @@ import org.thingsboard.server.queue.discovery.QueueKey;
import java.io.Serial; import java.io.Serial;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;

3
common/queue/src/main/java/org/thingsboard/server/queue/edqs/EdqsQueueFactory.java

@ -17,6 +17,7 @@ package org.thingsboard.server.queue.edqs;
import org.thingsboard.server.gen.transport.TransportProtos.FromEdqsMsg; import org.thingsboard.server.gen.transport.TransportProtos.FromEdqsMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdqsMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdqsMsg;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.TbQueueResponseTemplate; import org.thingsboard.server.queue.TbQueueResponseTemplate;
@ -32,4 +33,6 @@ public interface EdqsQueueFactory {
TbQueueResponseTemplate<TbProtoQueueMsg<ToEdqsMsg>, TbProtoQueueMsg<FromEdqsMsg>> createEdqsResponseTemplate(); TbQueueResponseTemplate<TbProtoQueueMsg<ToEdqsMsg>, TbProtoQueueMsg<FromEdqsMsg>> createEdqsResponseTemplate();
TbQueueAdmin getEdqsQueueAdmin();
} }

7
common/queue/src/main/java/org/thingsboard/server/queue/edqs/InMemoryEdqsQueueFactory.java

@ -22,6 +22,7 @@ import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.common.stats.StatsType; import org.thingsboard.server.common.stats.StatsType;
import org.thingsboard.server.gen.transport.TransportProtos.FromEdqsMsg; import org.thingsboard.server.gen.transport.TransportProtos.FromEdqsMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdqsMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdqsMsg;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.TbQueueResponseTemplate; import org.thingsboard.server.queue.TbQueueResponseTemplate;
@ -39,6 +40,7 @@ public class InMemoryEdqsQueueFactory implements EdqsQueueFactory {
private final InMemoryStorage storage; private final InMemoryStorage storage;
private final EdqsConfig edqsConfig; private final EdqsConfig edqsConfig;
private final StatsFactory statsFactory; private final StatsFactory statsFactory;
private final TbQueueAdmin queueAdmin;
@Override @Override
public TbQueueConsumer<TbProtoQueueMsg<ToEdqsMsg>> createEdqsMsgConsumer(EdqsQueue queue) { public TbQueueConsumer<TbProtoQueueMsg<ToEdqsMsg>> createEdqsMsgConsumer(EdqsQueue queue) {
@ -76,4 +78,9 @@ public class InMemoryEdqsQueueFactory implements EdqsQueueFactory {
.build(); .build();
} }
@Override
public TbQueueAdmin getEdqsQueueAdmin() {
return queueAdmin;
}
} }

6
common/queue/src/main/java/org/thingsboard/server/queue/edqs/KafkaEdqsQueueFactory.java

@ -22,6 +22,7 @@ import org.thingsboard.server.common.stats.StatsType;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.FromEdqsMsg; import org.thingsboard.server.gen.transport.TransportProtos.FromEdqsMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdqsMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdqsMsg;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.TbQueueResponseTemplate; import org.thingsboard.server.queue.TbQueueResponseTemplate;
@ -126,4 +127,9 @@ public class KafkaEdqsQueueFactory implements EdqsQueueFactory {
.build(); .build();
} }
@Override
public TbQueueAdmin getEdqsQueueAdmin() {
return edqsEventsAdmin;
}
} }

2
common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaAdmin.java

@ -91,7 +91,7 @@ public class TbKafkaAdmin implements TbQueueAdmin {
@Override @Override
public void deleteTopic(String topic) { public void deleteTopic(String topic) {
Set<String> topics = getTopics(); Set<String> topics = getTopics();
if (topics.contains(topic)) { if (topics.remove(topic)) {
settings.getAdminClient().deleteTopics(Collections.singletonList(topic)); settings.getAdminClient().deleteTopics(Collections.singletonList(topic));
} else { } else {
try { try {

2
common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java

@ -160,7 +160,7 @@ public class TbKafkaConsumerTemplate<T extends TbQueueMsg> extends AbstractTbQue
int partition = record.partition(); int partition = record.partition();
Long endOffset = endOffsets.get(partition); Long endOffset = endOffsets.get(partition);
if (endOffset == null) { if (endOffset == null) {
log.warn("End offset not found for {} [{}]", record.topic(), partition); log.debug("End offset not found for {} [{}]", record.topic(), partition);
return; return;
} }
log.trace("[{}-{}] Got record offset {}, expected end offset: {}", record.topic(), partition, record.offset(), endOffset - 1); log.trace("[{}-{}] Got record offset {}, expected end offset: {}", record.topic(), partition, record.offset(), endOffset - 1);

5
common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java

@ -138,6 +138,11 @@ public class InMemoryMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE
return new InMemoryTbQueueConsumer<>(storage, topicService.buildTopicName(calculatedFieldSettings.getEventTopic())); return new InMemoryTbQueueConsumer<>(storage, topicService.buildTopicName(calculatedFieldSettings.getEventTopic()));
} }
@Override
public TbQueueAdmin getCalculatedFieldQueueAdmin() {
return queueAdmin;
}
@Override @Override
public TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToCalculatedFieldMsg>> createToCalculatedFieldMsgProducer() { public TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToCalculatedFieldMsg>> createToCalculatedFieldMsgProducer() {
return new InMemoryTbQueueProducer<>(storage, topicService.buildTopicName(calculatedFieldSettings.getEventTopic())); return new InMemoryTbQueueProducer<>(storage, topicService.buildTopicName(calculatedFieldSettings.getEventTopic()));

5
common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java

@ -526,6 +526,11 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi
return consumerBuilder.build(); return consumerBuilder.build();
} }
@Override
public TbQueueAdmin getCalculatedFieldQueueAdmin() {
return cfAdmin;
}
@Override @Override
public TbQueueProducer<TbProtoQueueMsg<ToCalculatedFieldMsg>> createToCalculatedFieldMsgProducer() { public TbQueueProducer<TbProtoQueueMsg<ToCalculatedFieldMsg>> createToCalculatedFieldMsgProducer() {
TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder<TbProtoQueueMsg<ToCalculatedFieldMsg>> requestBuilder = TbKafkaProducerTemplate.builder(); TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder<TbProtoQueueMsg<ToCalculatedFieldMsg>> requestBuilder = TbKafkaProducerTemplate.builder();

5
common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java

@ -321,6 +321,11 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory {
return consumerBuilder.build(); return consumerBuilder.build();
} }
@Override
public TbQueueAdmin getCalculatedFieldQueueAdmin() {
return cfAdmin;
}
@Override @Override
public TbQueueProducer<TbProtoQueueMsg<ToCalculatedFieldMsg>> createToCalculatedFieldMsgProducer() { public TbQueueProducer<TbProtoQueueMsg<ToCalculatedFieldMsg>> createToCalculatedFieldMsgProducer() {
TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder<TbProtoQueueMsg<ToCalculatedFieldMsg>> requestBuilder = TbKafkaProducerTemplate.builder(); TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder<TbProtoQueueMsg<ToCalculatedFieldMsg>> requestBuilder = TbKafkaProducerTemplate.builder();

3
common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineQueueFactory.java

@ -29,6 +29,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateSer
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.TbQueueRequestTemplate; import org.thingsboard.server.queue.TbQueueRequestTemplate;
@ -122,6 +123,8 @@ public interface TbRuleEngineQueueFactory extends TbUsageStatsClientQueueFactory
TbQueueConsumer<TbProtoQueueMsg<ToCalculatedFieldMsg>> createToCalculatedFieldMsgConsumer(); TbQueueConsumer<TbProtoQueueMsg<ToCalculatedFieldMsg>> createToCalculatedFieldMsgConsumer();
TbQueueAdmin getCalculatedFieldQueueAdmin();
TbQueueProducer<TbProtoQueueMsg<ToCalculatedFieldMsg>> createToCalculatedFieldMsgProducer(); TbQueueProducer<TbProtoQueueMsg<ToCalculatedFieldMsg>> createToCalculatedFieldMsgProducer();
TbQueueConsumer<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> createToCalculatedFieldNotificationsMsgConsumer(); TbQueueConsumer<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> createToCalculatedFieldNotificationsMsgConsumer();

53
common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageReportClient.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.queue.usagestats; package org.thingsboard.server.queue.usagestats;
import com.google.common.collect.Lists;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import lombok.Data; import lombok.Data;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -29,6 +30,9 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.stats.TbApiUsageReportClient; import org.thingsboard.server.common.stats.TbApiUsageReportClient;
import org.thingsboard.server.common.util.ProtoUtils;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsServiceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto; import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto;
import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.TbQueueProducer;
@ -38,7 +42,11 @@ import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.queue.scheduler.SchedulerComponent;
import java.util.ArrayList;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random; import java.util.Random;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -57,6 +65,8 @@ public class DefaultTbApiUsageReportClient implements TbApiUsageReportClient {
private boolean enabledPerCustomer; private boolean enabledPerCustomer;
@Value("${usage.stats.report.interval:10}") @Value("${usage.stats.report.interval:10}")
private int interval; private int interval;
@Value("${usage.stats.report.pack_size:1024}")
private int packSize;
private final EnumMap<ApiUsageRecordKey, ConcurrentMap<ReportLevel, AtomicLong>> stats = new EnumMap<>(ApiUsageRecordKey.class); private final EnumMap<ApiUsageRecordKey, ConcurrentMap<ReportLevel, AtomicLong>> stats = new EnumMap<>(ApiUsageRecordKey.class);
@ -64,7 +74,7 @@ public class DefaultTbApiUsageReportClient implements TbApiUsageReportClient {
private final TbServiceInfoProvider serviceInfoProvider; private final TbServiceInfoProvider serviceInfoProvider;
private final SchedulerComponent scheduler; private final SchedulerComponent scheduler;
private final TbQueueProducerProvider producerProvider; private final TbQueueProducerProvider producerProvider;
private TbQueueProducer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> msgProducer; private TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToUsageStatsServiceMsg>> msgProducer;
@PostConstruct @PostConstruct
private void init() { private void init() {
@ -84,7 +94,7 @@ public class DefaultTbApiUsageReportClient implements TbApiUsageReportClient {
} }
private void reportStats() { private void reportStats() {
ConcurrentMap<ParentEntity, ToUsageStatsServiceMsg.Builder> report = new ConcurrentHashMap<>(); ConcurrentMap<ParentEntity, UsageStatsServiceMsg.Builder> report = new ConcurrentHashMap<>();
for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) {
ConcurrentMap<ReportLevel, AtomicLong> statsForKey = stats.get(key); ConcurrentMap<ReportLevel, AtomicLong> statsForKey = stats.get(key);
@ -92,8 +102,8 @@ public class DefaultTbApiUsageReportClient implements TbApiUsageReportClient {
long value = statsValue.get(); long value = statsValue.get();
if (value == 0 && key.isCounter()) return; if (value == 0 && key.isCounter()) return;
ToUsageStatsServiceMsg.Builder statsMsg = report.computeIfAbsent(reportLevel.getParentEntity(), parent -> { UsageStatsServiceMsg.Builder statsMsg = report.computeIfAbsent(reportLevel.getParentEntity(), parent -> {
ToUsageStatsServiceMsg.Builder newStatsMsg = ToUsageStatsServiceMsg.newBuilder(); UsageStatsServiceMsg.Builder newStatsMsg = UsageStatsServiceMsg.newBuilder();
TenantId tenantId = parent.getTenantId(); TenantId tenantId = parent.getTenantId();
newStatsMsg.setTenantIdMSB(tenantId.getId().getMostSignificantBits()); newStatsMsg.setTenantIdMSB(tenantId.getId().getMostSignificantBits());
@ -105,36 +115,55 @@ public class DefaultTbApiUsageReportClient implements TbApiUsageReportClient {
newStatsMsg.setCustomerIdLSB(customerId.getId().getLeastSignificantBits()); newStatsMsg.setCustomerIdLSB(customerId.getId().getLeastSignificantBits());
} }
newStatsMsg.setServiceId(serviceInfoProvider.getServiceId());
return newStatsMsg; return newStatsMsg;
}); });
UsageStatsKVProto.Builder statsItem = UsageStatsKVProto.newBuilder() UsageStatsKVProto.Builder statsItem = UsageStatsKVProto.newBuilder()
.setKey(key.name()) .setRecordKey(ProtoUtils.toProto(key))
.setValue(value); .setValue(value);
statsMsg.addValues(statsItem.build()); statsMsg.addValues(statsItem.build());
}); });
statsForKey.clear(); statsForKey.clear();
} }
report.forEach(((parent, statsMsg) -> { Map<TopicPartitionInfo, List<UsageStatsServiceMsg>> reportStatsPerTpi = new HashMap<>();
//TODO: figure out how to minimize messages into the queue. Maybe group by 100s of messages?
report.forEach((parent, statsMsg) -> {
try { try {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, parent.getTenantId(), parent.getId()) TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, parent.getTenantId(), parent.getId())
.newByTopic(msgProducer.getDefaultTopic()); .newByTopic(msgProducer.getDefaultTopic());
msgProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), statsMsg.build()), null); reportStatsPerTpi.computeIfAbsent(tpi, k -> new ArrayList<>()).add(statsMsg.build());
} catch (TenantNotFoundException e) { } catch (TenantNotFoundException e) {
log.debug("Couldn't report usage stats for non-existing tenant: {}", e.getTenantId()); log.debug("Couldn't report usage stats for non-existing tenant: {}", e.getTenantId());
} catch (Exception e) {
log.warn("Failed to report usage stats for tenant {}", parent.getTenantId(), e);
} }
})); });
reportStatsPerTpi.forEach((tpi, statsList) -> {
toMsgPack(statsList).forEach(pack -> {
try {
msgProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), pack), null);
} catch (Exception e) {
log.warn("Failed to report usage stats pack to TPI {}", tpi, e);
}
});
});
if (!report.isEmpty()) { if (!report.isEmpty()) {
log.debug("Reporting API usage statistics for {} tenants and customers", report.size()); log.debug("Reporting API usage statistics for {} tenants and customers", report.size());
} }
} }
private List<ToUsageStatsServiceMsg> toMsgPack(List<UsageStatsServiceMsg> list) {
return Lists.partition(list, packSize)
.stream()
.map(partition ->
ToUsageStatsServiceMsg.newBuilder()
.addAllMsgs(partition)
.setServiceId(serviceInfoProvider.getServiceId())
.build())
.toList();
}
@Override @Override
public void report(TenantId tenantId, CustomerId customerId, ApiUsageRecordKey key, long value) { public void report(TenantId tenantId, CustomerId customerId, ApiUsageRecordKey key, long value) {
if (!enabled) return; if (!enabled) return;

3
dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java

@ -236,6 +236,7 @@ public class BaseRelationService implements RelationService {
return Futures.transform(future, deletedEvent -> { return Futures.transform(future, deletedEvent -> {
if (deletedEvent != null) { if (deletedEvent != null) {
handleEvictEvent(EntityRelationEvent.from(deletedEvent)); handleEvictEvent(EntityRelationEvent.from(deletedEvent));
eventPublisher.publishEvent(new RelationActionEvent(tenantId, deletedEvent, ActionType.RELATION_DELETED));
} }
return deletedEvent != null; return deletedEvent != null;
}, MoreExecutors.directExecutor()); }, MoreExecutors.directExecutor());
@ -267,6 +268,7 @@ public class BaseRelationService implements RelationService {
for (EntityRelation relation : inboundRelations) { for (EntityRelation relation : inboundRelations) {
eventPublisher.publishEvent(EntityRelationEvent.from(relation)); eventPublisher.publishEvent(EntityRelationEvent.from(relation));
eventPublisher.publishEvent(new RelationActionEvent(tenantId, relation, ActionType.RELATION_DELETED));
} }
List<EntityRelation> outboundRelations; List<EntityRelation> outboundRelations;
@ -278,6 +280,7 @@ public class BaseRelationService implements RelationService {
for (EntityRelation relation : outboundRelations) { for (EntityRelation relation : outboundRelations) {
eventPublisher.publishEvent(EntityRelationEvent.from(relation)); eventPublisher.publishEvent(EntityRelationEvent.from(relation));
eventPublisher.publishEvent(new RelationActionEvent(tenantId, relation, ActionType.RELATION_DELETED));
} }
} }

4
dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java

@ -195,7 +195,9 @@ public class BaseTimeseriesService implements TimeseriesService {
} }
if (saveLatest) { if (saveLatest) {
latestFutures.add(Futures.transform(timeseriesLatestDao.saveLatest(tenantId, entityId, tsKvEntry), version -> { latestFutures.add(Futures.transform(timeseriesLatestDao.saveLatest(tenantId, entityId, tsKvEntry), version -> {
edqsService.onUpdate(tenantId, ObjectType.LATEST_TS_KV, new LatestTsKv(entityId, tsKvEntry, version)); if (version != null) {
edqsService.onUpdate(tenantId, ObjectType.LATEST_TS_KV, new LatestTsKv(entityId, tsKvEntry, version));
}
return version; return version;
}, MoreExecutors.directExecutor())); }, MoreExecutors.directExecutor()));
} }

38
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java

@ -28,6 +28,7 @@ import io.restassured.internal.ValidatableResponseImpl;
import io.restassured.path.json.JsonPath; import io.restassured.path.json.JsonPath;
import io.restassured.response.ValidatableResponse; import io.restassured.response.ValidatableResponse;
import io.restassured.specification.RequestSpecification; import io.restassured.specification.RequestSpecification;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
@ -40,10 +41,12 @@ import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile; import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.event.EventType; import org.thingsboard.server.common.data.event.EventType;
import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.AssetProfileId; import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
@ -146,6 +149,14 @@ public class TestRestClient {
.as(ObjectNode.class); .as(ObjectNode.class);
} }
public CalculatedField postCalculatedField(CalculatedField calculatedField) {
return given().spec(requestSpec).body(calculatedField)
.post("/api/calculatedField")
.then()
.statusCode(HTTP_OK)
.extract()
.as(CalculatedField.class);
}
public Device getDeviceByName(String deviceName) { public Device getDeviceByName(String deviceName) {
return given().spec(requestSpec).pathParam("deviceName", deviceName) return given().spec(requestSpec).pathParam("deviceName", deviceName)
@ -212,9 +223,16 @@ public class TestRestClient {
.statusCode(anyOf(is(HTTP_OK), is(HTTP_NOT_FOUND))); .statusCode(anyOf(is(HTTP_OK), is(HTTP_NOT_FOUND)));
} }
public ValidatableResponse postTelemetryAttribute(String entityType, DeviceId deviceId, String scope, JsonNode attribute) { public ValidatableResponse deleteCalculatedFieldIfExists(CalculatedFieldId calculatedFieldId) {
return given().spec(requestSpec)
.delete("/api/calculatedField/{calculatedFieldId}", calculatedFieldId.getId())
.then()
.statusCode(anyOf(is(HTTP_OK), is(HTTP_NOT_FOUND)));
}
public ValidatableResponse postTelemetryAttribute(EntityId entityId, String scope, JsonNode attribute) {
return given().spec(requestSpec).body(attribute) return given().spec(requestSpec).body(attribute)
.post("/api/plugins/telemetry/{entityType}/{entityId}/attributes/{scope}", entityType, deviceId.getId(), scope) .post("/api/plugins/telemetry/{entityType}/{entityId}/attributes/{scope}", entityId.getEntityType(), entityId.getId(), scope)
.then() .then()
.statusCode(HTTP_OK); .statusCode(HTTP_OK);
} }
@ -237,6 +255,15 @@ public class TestRestClient {
.as(JsonNode.class); .as(JsonNode.class);
} }
public JsonNode getAttributes(EntityId entityId, AttributeScope scope, String keys) {
return given().spec(requestSpec)
.get("/api/plugins/telemetry/{entityType}/{entityId}/values/attributes/{scope}?keys={keys}", entityId.getEntityType(), entityId.getId(), scope, keys)
.then()
.statusCode(HTTP_OK)
.extract()
.as(JsonNode.class);
}
public JsonNode getLatestTelemetry(EntityId entityId) { public JsonNode getLatestTelemetry(EntityId entityId) {
return given().spec(requestSpec) return given().spec(requestSpec)
.get("/api/plugins/telemetry/" + entityId.getEntityType().name() + "/" + entityId.getId() + "/values/timeseries") .get("/api/plugins/telemetry/" + entityId.getEntityType().name() + "/" + entityId.getId() + "/values/timeseries")
@ -640,6 +667,7 @@ public class TestRestClient {
.then() .then()
.statusCode(anyOf(is(HTTP_OK), is(HTTP_BAD_REQUEST))); .statusCode(anyOf(is(HTTP_OK), is(HTTP_BAD_REQUEST)));
} }
public void deleteDeviceProfileIfExists(DeviceProfile deviceProfile) { public void deleteDeviceProfileIfExists(DeviceProfile deviceProfile) {
given().spec(requestSpec) given().spec(requestSpec)
.delete("/api/deviceProfile/" + deviceProfile.getId().getId().toString()) .delete("/api/deviceProfile/" + deviceProfile.getId().getId().toString())
@ -653,11 +681,11 @@ public class TestRestClient {
.get("/api/tenant/devices?deviceName={deviceName}") .get("/api/tenant/devices?deviceName={deviceName}")
.then() .then()
.statusCode(anyOf(is(HTTP_OK), is(HTTP_NOT_FOUND))); .statusCode(anyOf(is(HTTP_OK), is(HTTP_NOT_FOUND)));
if(((ValidatableResponseImpl) response).extract().response().getStatusCode()==HTTP_OK){ if (((ValidatableResponseImpl) response).extract().response().getStatusCode() == HTTP_OK) {
return response.extract() return response.extract()
.as(Device.class); .as(Device.class);
} else { } else {
return null; return null;
} }
} }

368
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/cf/CalculatedFieldTest.java

@ -0,0 +1,368 @@
/**
* Copyright © 2016-2025 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.msa.cf;
import com.fasterxml.jackson.databind.JsonNode;
import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
import org.thingsboard.server.common.data.cf.configuration.Argument;
import org.thingsboard.server.common.data.cf.configuration.ArgumentType;
import org.thingsboard.server.common.data.cf.configuration.Output;
import org.thingsboard.server.common.data.cf.configuration.OutputType;
import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey;
import org.thingsboard.server.common.data.cf.configuration.SimpleCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.debug.DebugSettings;
import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration;
import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConfiguration;
import org.thingsboard.server.common.data.device.data.DeviceData;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.msa.AbstractContainerTest;
import org.thingsboard.server.msa.ui.utils.EntityPrototypes;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import static org.thingsboard.server.msa.ui.utils.EntityPrototypes.defaultAssetProfile;
import static org.thingsboard.server.msa.ui.utils.EntityPrototypes.defaultDeviceProfile;
import static org.thingsboard.server.msa.ui.utils.EntityPrototypes.defaultTenantAdmin;
public class CalculatedFieldTest extends AbstractContainerTest {
public final int TIMEOUT = 60;
public final int POLL_INTERVAL = 1;
private final String deviceToken = "zmzURIVRsq3lvnTP2XBE";
private final String exampleScript = "var avgTemperature = temperature.mean(); // Get average temperature\n" +
" var temperatureK = (avgTemperature - 32) * (5 / 9) + 273.15; // Convert Fahrenheit to Kelvin\n" +
"\n" +
" // Estimate air pressure based on altitude\n" +
" var pressure = 101325 * Math.pow((1 - 2.25577e-5 * altitude), 5.25588);\n" +
"\n" +
" // Air density formula\n" +
" var airDensity = pressure / (287.05 * temperatureK);\n" +
"\n" +
" return {\n" +
" \"airDensity\": airDensity\n" +
" };";
private TenantId tenantId;
private UserId tenantAdminId;
private DeviceProfileId deviceProfileId;
private AssetProfileId assetProfileId;
private Device device;
private Asset asset;
@BeforeClass
public void beforeClass() {
testRestClient.login("sysadmin@thingsboard.org", "sysadmin");
tenantId = testRestClient.postTenant(EntityPrototypes.defaultTenantPrototype("Tenant")).getId();
tenantAdminId = testRestClient.createUserAndLogin(defaultTenantAdmin(tenantId, "tenantAdmin@thingsboard.org"), "tenant");
deviceProfileId = testRestClient.postDeviceProfile(defaultDeviceProfile("Device Profile 1")).getId();
device = testRestClient.postDevice(deviceToken, createDevice("Device 1", deviceProfileId));
assetProfileId = testRestClient.postAssetProfile(defaultAssetProfile("Asset Profile 1")).getId();
asset = testRestClient.postAsset(createAsset("Asset 1", assetProfileId));
testRestClient.postTelemetry(deviceToken, JacksonUtil.toJsonNode("{\"temperature\":25}"));
testRestClient.postTelemetryAttribute(device.getId(), DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"deviceTemperature\":40}"));
testRestClient.postTelemetry(deviceToken, JacksonUtil.toJsonNode("{\"temperatureInF\":72.32}"));
testRestClient.postTelemetry(deviceToken, JacksonUtil.toJsonNode("{\"temperatureInF\":72.86}"));
testRestClient.postTelemetry(deviceToken, JacksonUtil.toJsonNode("{\"temperatureInF\":73.58}"));
testRestClient.postTelemetryAttribute(asset.getId(), DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"altitude\":1035}"));
}
@BeforeMethod
public void beforeMethod() {
testRestClient.login("sysadmin@thingsboard.org", "sysadmin");
}
@AfterClass
public void afterClass() {
testRestClient.resetToken();
testRestClient.login("sysadmin@thingsboard.org", "sysadmin");
testRestClient.deleteTenant(tenantId);
}
@Test
public void testPerformInitialCalculationForSimpleType() {
// login tenant admin
testRestClient.getAndSetUserToken(tenantAdminId);
CalculatedField savedCalculatedField = createSimpleCalculatedField();
testRestClient.postTelemetry(deviceToken, JacksonUtil.toJsonNode("{\"temperature\":25}"));
await().alias("create CF -> perform initial calculation").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> {
JsonNode fahrenheitTemp = testRestClient.getLatestTelemetry(device.getId());
assertThat(fahrenheitTemp).isNotNull();
assertThat(fahrenheitTemp.get("fahrenheitTemp").get(0).get("value").asText()).isEqualTo("77.0");
});
testRestClient.deleteCalculatedFieldIfExists(savedCalculatedField.getId());
}
@Test
public void testChangeConfigArgument() {
// login tenant admin
testRestClient.getAndSetUserToken(tenantAdminId);
CalculatedField savedCalculatedField = createSimpleCalculatedField();
Argument savedArgument = savedCalculatedField.getConfiguration().getArguments().get("T");
savedArgument.setRefEntityKey(new ReferencedEntityKey("deviceTemperature", ArgumentType.ATTRIBUTE, AttributeScope.SERVER_SCOPE));
testRestClient.postCalculatedField(savedCalculatedField);
await().alias("update CF argument -> perform calculation with new argument").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> {
JsonNode fahrenheitTemp = testRestClient.getLatestTelemetry(device.getId());
assertThat(fahrenheitTemp).isNotNull();
assertThat(fahrenheitTemp.get("fahrenheitTemp").get(0).get("value").asText()).isEqualTo("104.0");
});
testRestClient.deleteCalculatedFieldIfExists(savedCalculatedField.getId());
}
@Test
public void testChangeConfigOutput() {
// login tenant admin
testRestClient.getAndSetUserToken(tenantAdminId);
CalculatedField savedCalculatedField = createSimpleCalculatedField();
Output savedOutput = savedCalculatedField.getConfiguration().getOutput();
savedOutput.setType(OutputType.ATTRIBUTES);
savedOutput.setScope(AttributeScope.SERVER_SCOPE);
savedOutput.setName("temperatureF");
testRestClient.postCalculatedField(savedCalculatedField);
await().alias("update CF output -> perform calculation with updated output").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> {
JsonNode temperatureF = testRestClient.getAttributes(device.getId(), AttributeScope.SERVER_SCOPE, "temperatureF");
assertThat(temperatureF).isNotNull();
assertThat(temperatureF.get(0).get("value").asText()).isEqualTo("77.0");
});
testRestClient.deleteCalculatedFieldIfExists(savedCalculatedField.getId());
}
@Test
public void testChangeConfigExpression() {
// login tenant admin
testRestClient.getAndSetUserToken(tenantAdminId);
CalculatedField savedCalculatedField = createSimpleCalculatedField();
savedCalculatedField.setName("F to C");
savedCalculatedField.getConfiguration().setExpression("(T - 32) / 1.8");
testRestClient.postCalculatedField(savedCalculatedField);
await().alias("update CF expression -> perform calculation with new expression").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> {
JsonNode fahrenheitTemp = testRestClient.getLatestTelemetry(device.getId());
assertThat(fahrenheitTemp).isNotNull();
assertThat(fahrenheitTemp.get("fahrenheitTemp").get(0).get("value").asText()).isEqualTo("-3.89");
});
testRestClient.deleteCalculatedFieldIfExists(savedCalculatedField.getId());
}
@Test
public void testTelemetryUpdated() {
// login tenant admin
testRestClient.getAndSetUserToken(tenantAdminId);
CalculatedField savedCalculatedField = createSimpleCalculatedField();
testRestClient.postTelemetry(deviceToken, JacksonUtil.toJsonNode("{\"temperature\":30}"));
await().alias("update telemetry -> recalculate state").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> {
JsonNode fahrenheitTemp = testRestClient.getLatestTelemetry(device.getId());
assertThat(fahrenheitTemp).isNotNull();
assertThat(fahrenheitTemp.get("fahrenheitTemp").get(0).get("value").asText()).isEqualTo("86.0");
});
testRestClient.deleteCalculatedFieldIfExists(savedCalculatedField.getId());
}
@Test
public void testEntityIdIsProfile() {
// login tenant admin
testRestClient.getAndSetUserToken(tenantAdminId);
CalculatedField savedCalculatedField = createSimpleCalculatedField(deviceProfileId);
await().alias("create CF -> perform initial calculation for device by profile").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> {
JsonNode fahrenheitTemp = testRestClient.getLatestTelemetry(device.getId());
assertThat(fahrenheitTemp).isNotNull();
assertThat(fahrenheitTemp.get("fahrenheitTemp").get(0).get("value").asText()).isEqualTo("77.0");
});
testRestClient.deleteCalculatedFieldIfExists(savedCalculatedField.getId());
}
@Test
public void testEntityAddedAndDeleted() {
// login tenant admin
testRestClient.getAndSetUserToken(tenantAdminId);
CalculatedField savedCalculatedField = createSimpleCalculatedField(deviceProfileId);
String newDeviceToken = "mmmXRIVRsq9lbnTP2XBE";
Device newDevice = testRestClient.postDevice(newDeviceToken, createDevice("Device 2", deviceProfileId));
await().alias("create device by profile -> perform initial calculation for new device by profile").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> {
// used default value since telemetry is not present
JsonNode fahrenheitTemp = testRestClient.getLatestTelemetry(newDevice.getId());
assertThat(fahrenheitTemp).isNotNull();
assertThat(fahrenheitTemp.get("fahrenheitTemp").get(0).get("value").asText()).isEqualTo("53.6");
});
DeviceProfile newDeviceProfile = testRestClient.postDeviceProfile(defaultDeviceProfile("Test Profile"));
newDevice.setDeviceProfileId(newDeviceProfile.getId());
testRestClient.postDevice(newDeviceToken, newDevice);
testRestClient.postTelemetry(newDeviceToken, JacksonUtil.toJsonNode("{\"temperature\":25}"));
await().alias("update telemetry -> no updates").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> {
JsonNode fahrenheitTemp = testRestClient.getLatestTelemetry(newDevice.getId());
assertThat(fahrenheitTemp).isNotNull();
assertThat(fahrenheitTemp.get("fahrenheitTemp").get(0).get("value").asText()).isEqualTo("53.6");
});
testRestClient.deleteCalculatedFieldIfExists(savedCalculatedField.getId());
}
private CalculatedField createSimpleCalculatedField() {
return createSimpleCalculatedField(device.getId());
}
private CalculatedField createSimpleCalculatedField(EntityId entityId) {
CalculatedField calculatedField = new CalculatedField();
calculatedField.setEntityId(entityId);
calculatedField.setType(CalculatedFieldType.SIMPLE);
calculatedField.setName("C to F" + RandomStringUtils.randomAlphabetic(5));
calculatedField.setDebugSettings(DebugSettings.all());
SimpleCalculatedFieldConfiguration config = new SimpleCalculatedFieldConfiguration();
Argument argument = new Argument();
ReferencedEntityKey refEntityKey = new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null);
argument.setRefEntityKey(refEntityKey);
argument.setDefaultValue("12"); // not used because real telemetry value in db is present
config.setArguments(Map.of("T", argument));
config.setExpression("(T * 9/5) + 32");
Output output = new Output();
output.setName("fahrenheitTemp");
output.setType(OutputType.TIME_SERIES);
output.setDecimalsByDefault(2);
config.setOutput(output);
calculatedField.setConfiguration(config);
return testRestClient.postCalculatedField(calculatedField);
}
private CalculatedField createScriptCalculatedField() {
return createScriptCalculatedField(device.getId());
}
private CalculatedField createScriptCalculatedField(EntityId entityId) {
CalculatedField calculatedField = new CalculatedField();
calculatedField.setEntityId(entityId);
calculatedField.setType(CalculatedFieldType.SCRIPT);
calculatedField.setName("Air density" + RandomStringUtils.randomAlphabetic(5));
calculatedField.setDebugSettings(DebugSettings.all());
SimpleCalculatedFieldConfiguration config = new SimpleCalculatedFieldConfiguration();
Argument argument1 = new Argument();
argument1.setRefEntityId(asset.getId());
ReferencedEntityKey refEntityKey1 = new ReferencedEntityKey("altitude", ArgumentType.ATTRIBUTE, AttributeScope.SERVER_SCOPE);
argument1.setRefEntityKey(refEntityKey1);
config.setArguments(Map.of("altitude", argument1));
Argument argument2 = new Argument();
ReferencedEntityKey refEntityKey2 = new ReferencedEntityKey("temperatureInF", ArgumentType.TS_ROLLING, null);
argument2.setRefEntityKey(refEntityKey2);
config.setArguments(Map.of("temperature", argument2));
config.setExpression("return {\"airDensity\": 5};");
Output output = new Output();
output.setType(OutputType.TIME_SERIES);
config.setOutput(output);
calculatedField.setConfiguration(config);
return testRestClient.postCalculatedField(calculatedField);
}
private Device createDevice(String name, DeviceProfileId deviceProfileId) {
Device device = new Device();
device.setName(name);
device.setType("default");
device.setDeviceProfileId(deviceProfileId);
DeviceData deviceData = new DeviceData();
deviceData.setTransportConfiguration(new DefaultDeviceTransportConfiguration());
deviceData.setConfiguration(new DefaultDeviceConfiguration());
device.setDeviceData(deviceData);
return device;
}
private Asset createAsset(String name, AssetProfileId assetProfileId) {
Asset asset = new Asset();
asset.setName(name);
asset.setAssetProfileId(assetProfileId);
return asset;
}
}

2
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java

@ -77,7 +77,7 @@ public class HttpClientTest extends AbstractContainerTest {
assertThat(accessToken).isNotNull(); assertThat(accessToken).isNotNull();
JsonNode sharedAttribute = mapper.readTree(createPayload().toString()); JsonNode sharedAttribute = mapper.readTree(createPayload().toString());
testRestClient.postTelemetryAttribute(DEVICE, device.getId(), SHARED_SCOPE, sharedAttribute); testRestClient.postTelemetryAttribute(device.getId(), SHARED_SCOPE, sharedAttribute);
JsonNode clientAttribute = mapper.readTree(createPayload().toString()); JsonNode clientAttribute = mapper.readTree(createPayload().toString());
testRestClient.postAttribute(accessToken, clientAttribute); testRestClient.postAttribute(accessToken, clientAttribute);

6
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java

@ -212,7 +212,7 @@ public class MqttClientTest extends AbstractContainerTest {
String sharedAttributeValue = StringUtils.randomAlphanumeric(8); String sharedAttributeValue = StringUtils.randomAlphanumeric(8);
sharedAttributes.addProperty("sharedAttr", sharedAttributeValue); sharedAttributes.addProperty("sharedAttr", sharedAttributeValue);
JsonNode sharedAttribute = mapper.readTree(sharedAttributes.toString()); JsonNode sharedAttribute = mapper.readTree(sharedAttributes.toString());
testRestClient.postTelemetryAttribute(DEVICE, device.getId(), SHARED_SCOPE, sharedAttribute); testRestClient.postTelemetryAttribute(device.getId(), SHARED_SCOPE, sharedAttribute);
// Subscribe to attributes response // Subscribe to attributes response
mqttClient.on("v1/devices/me/attributes/response/+", listener, MqttQoS.AT_LEAST_ONCE).get(); mqttClient.on("v1/devices/me/attributes/response/+", listener, MqttQoS.AT_LEAST_ONCE).get();
@ -255,7 +255,7 @@ public class MqttClientTest extends AbstractContainerTest {
sharedAttributes.addProperty(sharedAttributeName, sharedAttributeValue); sharedAttributes.addProperty(sharedAttributeName, sharedAttributeValue);
JsonNode sharedAttribute = mapper.readTree(sharedAttributes.toString()); JsonNode sharedAttribute = mapper.readTree(sharedAttributes.toString());
testRestClient.postTelemetryAttribute(DataConstants.DEVICE, device.getId(), SHARED_SCOPE, sharedAttribute); testRestClient.postTelemetryAttribute(device.getId(), SHARED_SCOPE, sharedAttribute);
MqttEvent event = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS); MqttEvent event = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS);
assertThat(mapper.readValue(Objects.requireNonNull(event).getMessage(), JsonNode.class).get(sharedAttributeName).asText()) assertThat(mapper.readValue(Objects.requireNonNull(event).getMessage(), JsonNode.class).get(sharedAttributeName).asText())
@ -265,7 +265,7 @@ public class MqttClientTest extends AbstractContainerTest {
JsonObject updatedSharedAttributes = new JsonObject(); JsonObject updatedSharedAttributes = new JsonObject();
String updatedSharedAttributeValue = StringUtils.randomAlphanumeric(8); String updatedSharedAttributeValue = StringUtils.randomAlphanumeric(8);
updatedSharedAttributes.addProperty(sharedAttributeName, updatedSharedAttributeValue); updatedSharedAttributes.addProperty(sharedAttributeName, updatedSharedAttributeValue);
testRestClient.postTelemetryAttribute(DEVICE, device.getId(), SHARED_SCOPE, mapper.readTree(updatedSharedAttributes.toString())); testRestClient.postTelemetryAttribute(device.getId(), SHARED_SCOPE, mapper.readTree(updatedSharedAttributes.toString()));
event = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS); event = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS);
assertThat(mapper.readValue(Objects.requireNonNull(event).getMessage(), JsonNode.class).get(sharedAttributeName).asText()) assertThat(mapper.readValue(Objects.requireNonNull(event).getMessage(), JsonNode.class).get(sharedAttributeName).asText())

8
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java

@ -184,7 +184,7 @@ public class MqttGatewayClientTest extends AbstractContainerTest {
mqttClient.on("v1/gateway/attributes/response", listener, MqttQoS.AT_LEAST_ONCE).get(); mqttClient.on("v1/gateway/attributes/response", listener, MqttQoS.AT_LEAST_ONCE).get();
testRestClient.postTelemetryAttribute(DataConstants.DEVICE, createdDevice.getId(), SHARED_SCOPE, mapper.readTree(sharedAttributes.toString())); testRestClient.postTelemetryAttribute(createdDevice.getId(), SHARED_SCOPE, mapper.readTree(sharedAttributes.toString()));
var event = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS); var event = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS);
JsonObject requestData = new JsonObject(); JsonObject requestData = new JsonObject();
@ -266,7 +266,7 @@ public class MqttGatewayClientTest extends AbstractContainerTest {
// Subscribe for attribute update event // Subscribe for attribute update event
mqttClient.on("v1/gateway/attributes", listener, MqttQoS.AT_LEAST_ONCE).get(); mqttClient.on("v1/gateway/attributes", listener, MqttQoS.AT_LEAST_ONCE).get();
testRestClient.postTelemetryAttribute(DEVICE, createdDevice.getId(), SHARED_SCOPE, mapper.readTree(sharedAttributes.toString())); testRestClient.postTelemetryAttribute(createdDevice.getId(), SHARED_SCOPE, mapper.readTree(sharedAttributes.toString()));
MqttEvent sharedAttributeEvent = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS); MqttEvent sharedAttributeEvent = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS);
// Catch attribute update event // Catch attribute update event
@ -299,7 +299,7 @@ public class MqttGatewayClientTest extends AbstractContainerTest {
gatewaySharedAttributeValue.addProperty("device", createdDevice.getName()); gatewaySharedAttributeValue.addProperty("device", createdDevice.getName());
gatewaySharedAttributeValue.add("data", sharedAttributes); gatewaySharedAttributeValue.add("data", sharedAttributes);
testRestClient.postTelemetryAttribute(DEVICE, createdDevice.getId(), SHARED_SCOPE, mapper.readTree(sharedAttributes.toString())); testRestClient.postTelemetryAttribute(createdDevice.getId(), SHARED_SCOPE, mapper.readTree(sharedAttributes.toString()));
MqttEvent event = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS); MqttEvent event = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS);
assertThat(mapper.readValue(Objects.requireNonNull(event).getMessage(), JsonNode.class).get("data").get(sharedAttributeName).asText()) assertThat(mapper.readValue(Objects.requireNonNull(event).getMessage(), JsonNode.class).get("data").get(sharedAttributeName).asText())
@ -314,7 +314,7 @@ public class MqttGatewayClientTest extends AbstractContainerTest {
gatewayUpdatedSharedAttributeValue.addProperty("device", createdDevice.getName()); gatewayUpdatedSharedAttributeValue.addProperty("device", createdDevice.getName());
gatewayUpdatedSharedAttributeValue.add("data", updatedSharedAttributes); gatewayUpdatedSharedAttributeValue.add("data", updatedSharedAttributes);
testRestClient.postTelemetryAttribute(DEVICE, createdDevice.getId(), SHARED_SCOPE, mapper.readTree(updatedSharedAttributes.toString())); testRestClient.postTelemetryAttribute(createdDevice.getId(), SHARED_SCOPE, mapper.readTree(updatedSharedAttributes.toString()));
event = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS); event = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS);
assertThat(mapper.readValue(Objects.requireNonNull(event).getMessage(), JsonNode.class).get("data").get(sharedAttributeName).asText()) assertThat(mapper.readValue(Objects.requireNonNull(event).getMessage(), JsonNode.class).get("data").get(sharedAttributeName).asText())
.isEqualTo(updatedSharedAttributeValue); .isEqualTo(updatedSharedAttributeValue);

4
msa/vc-executor/src/main/resources/tb-vc-executor.yml

@ -207,7 +207,9 @@ usage:
# Enable/Disable collection of statistics about API usage on a customer level # Enable/Disable collection of statistics about API usage on a customer level
enabled_per_customer: "${USAGE_STATS_REPORT_PER_CUSTOMER_ENABLED:false}" enabled_per_customer: "${USAGE_STATS_REPORT_PER_CUSTOMER_ENABLED:false}"
# Interval of reporting the statistics. By default, the summarized statistics are sent every 10 seconds # Interval of reporting the statistics. By default, the summarized statistics are sent every 10 seconds
interval: "${USAGE_STATS_REPORT_INTERVAL:10}" interval: "${USAGE_STATS_REPORT_INTERVAL:60}"
# Amount of statistic messages in pack
pack_size: "${USAGE_STATS_REPORT_PACK_SIZE:1024}"
# Metrics parameters # Metrics parameters
metrics: metrics:

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

@ -411,7 +411,9 @@ usage:
# Enable/Disable collection of statistics about API usage on a customer level # Enable/Disable collection of statistics about API usage on a customer level
enabled_per_customer: "${USAGE_STATS_REPORT_PER_CUSTOMER_ENABLED:false}" enabled_per_customer: "${USAGE_STATS_REPORT_PER_CUSTOMER_ENABLED:false}"
# Interval of reporting the statistics. By default, the summarized statistics are sent every 10 seconds # Interval of reporting the statistics. By default, the summarized statistics are sent every 10 seconds
interval: "${USAGE_STATS_REPORT_INTERVAL:10}" interval: "${USAGE_STATS_REPORT_INTERVAL:60}"
# Amount of statistic messages in pack
pack_size: "${USAGE_STATS_REPORT_PACK_SIZE:1024}"
# Metrics parameters # Metrics parameters
metrics: metrics:

4
transport/http/src/main/resources/tb-http-transport.yml

@ -360,7 +360,9 @@ usage:
# Enable/Disable collection of statistics about API usage on a customer level # Enable/Disable collection of statistics about API usage on a customer level
enabled_per_customer: "${USAGE_STATS_REPORT_PER_CUSTOMER_ENABLED:false}" enabled_per_customer: "${USAGE_STATS_REPORT_PER_CUSTOMER_ENABLED:false}"
# Interval of reporting the statistics. By default, the summarized statistics are sent every 10 seconds # Interval of reporting the statistics. By default, the summarized statistics are sent every 10 seconds
interval: "${USAGE_STATS_REPORT_INTERVAL:10}" interval: "${USAGE_STATS_REPORT_INTERVAL:60}"
# Amount of statistic messages in pack
pack_size: "${USAGE_STATS_REPORT_PACK_SIZE:1024}"
# Metrics parameters # Metrics parameters
metrics: metrics:

4
transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml

@ -461,7 +461,9 @@ usage:
# Enable/Disable collection of statistics about API usage on a customer level # Enable/Disable collection of statistics about API usage on a customer level
enabled_per_customer: "${USAGE_STATS_REPORT_PER_CUSTOMER_ENABLED:false}" enabled_per_customer: "${USAGE_STATS_REPORT_PER_CUSTOMER_ENABLED:false}"
# Interval of reporting the statistics. By default, the summarized statistics are sent every 10 seconds # Interval of reporting the statistics. By default, the summarized statistics are sent every 10 seconds
interval: "${USAGE_STATS_REPORT_INTERVAL:10}" interval: "${USAGE_STATS_REPORT_INTERVAL:60}"
# Amount of statistic messages in pack
pack_size: "${USAGE_STATS_REPORT_PACK_SIZE:1024}"
# Metrics parameters # Metrics parameters
metrics: metrics:

4
transport/mqtt/src/main/resources/tb-mqtt-transport.yml

@ -394,7 +394,9 @@ usage:
# Enable/Disable collection of statistics about API usage on a customer level # Enable/Disable collection of statistics about API usage on a customer level
enabled_per_customer: "${USAGE_STATS_REPORT_PER_CUSTOMER_ENABLED:false}" enabled_per_customer: "${USAGE_STATS_REPORT_PER_CUSTOMER_ENABLED:false}"
# Interval of reporting the statistics. By default, the summarized statistics are sent every 10 seconds # Interval of reporting the statistics. By default, the summarized statistics are sent every 10 seconds
interval: "${USAGE_STATS_REPORT_INTERVAL:10}" interval: "${USAGE_STATS_REPORT_INTERVAL:60}"
# Amount of statistic messages in pack
pack_size: "${USAGE_STATS_REPORT_PACK_SIZE:1024}"
# Metrics parameters # Metrics parameters
metrics: metrics:

4
transport/snmp/src/main/resources/tb-snmp-transport.yml

@ -347,7 +347,9 @@ usage:
# Enable/Disable collection of statistics about API usage on a customer level # Enable/Disable collection of statistics about API usage on a customer level
enabled_per_customer: "${USAGE_STATS_REPORT_PER_CUSTOMER_ENABLED:false}" enabled_per_customer: "${USAGE_STATS_REPORT_PER_CUSTOMER_ENABLED:false}"
# Interval of reporting the statistics. By default, the summarized statistics are sent every 10 seconds # Interval of reporting the statistics. By default, the summarized statistics are sent every 10 seconds
interval: "${USAGE_STATS_REPORT_INTERVAL:10}" interval: "${USAGE_STATS_REPORT_INTERVAL:60}"
# Amount of statistic messages in pack
pack_size: "${USAGE_STATS_REPORT_PACK_SIZE:1024}"
# Metrics parameters # Metrics parameters
metrics: metrics:

2
ui-ngx/package.json

@ -61,7 +61,7 @@
"leaflet": "1.9.4", "leaflet": "1.9.4",
"leaflet-polylinedecorator": "1.6.0", "leaflet-polylinedecorator": "1.6.0",
"leaflet-providers": "2.0.0", "leaflet-providers": "2.0.0",
"leaflet.gridlayer.googlemutant": "0.14.1", "leaflet.gridlayer.googlemutant": "0.15.0",
"leaflet.markercluster": "1.5.3", "leaflet.markercluster": "1.5.3",
"libphonenumber-js": "^1.11.15", "libphonenumber-js": "^1.11.15",
"maplibre-gl": "^5.2.0", "maplibre-gl": "^5.2.0",

5
ui-ngx/src/app/core/http/entity.service.ts

@ -287,6 +287,11 @@ export class EntityService {
case EntityType.OAUTH2_CLIENT: case EntityType.OAUTH2_CLIENT:
observable = this.oauth2Service.findTenantOAuth2ClientInfosByIds(entityIds, config); observable = this.oauth2Service.findTenantOAuth2ClientInfosByIds(entityIds, config);
break; break;
case EntityType.RULE_CHAIN:
observable = this.getEntitiesByIdsObservable(
(id) => this.ruleChainService.getRuleChain(id, config),
entityIds);
break;
} }
return observable; return observable;
} }

90
ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts

@ -33,36 +33,32 @@ import { AppState } from '@core/core.state';
import { getCurrentAuthState, getCurrentAuthUser } from '@core/auth/auth.selectors'; import { getCurrentAuthState, getCurrentAuthUser } from '@core/auth/auth.selectors';
import { DestroyRef, Renderer2 } from '@angular/core'; import { DestroyRef, Renderer2 } from '@angular/core';
import { EntityDebugSettings } from '@shared/models/entity.models'; import { EntityDebugSettings } from '@shared/models/entity.models';
import { DurationLeftPipe } from '@shared/pipe/duration-left.pipe';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { TbPopoverService } from '@shared/components/popover.service';
import { EntityDebugSettingsPanelComponent } from '@home/components/entity/debug/entity-debug-settings-panel.component';
import { CalculatedFieldsService } from '@core/http/calculated-fields.service'; import { CalculatedFieldsService } from '@core/http/calculated-fields.service';
import { catchError, filter, switchMap, tap } from 'rxjs/operators'; import { catchError, filter, switchMap, tap } from 'rxjs/operators';
import { import {
ArgumentType, ArgumentType,
CalculatedField, CalculatedField,
CalculatedFieldEventArguments, CalculatedFieldEventArguments,
CalculatedFieldDebugDialogData, CalculatedFieldType,
CalculatedFieldDialogData, CalculatedFieldTypeTranslations,
CalculatedFieldTestScriptDialogData,
getCalculatedFieldArgumentsEditorCompleter, getCalculatedFieldArgumentsEditorCompleter,
getCalculatedFieldArgumentsHighlights, getCalculatedFieldArgumentsHighlights,
CalculatedFieldTypeTranslations,
CalculatedFieldType,
} from '@shared/models/calculated-field.models'; } from '@shared/models/calculated-field.models';
import { import {
CalculatedFieldDebugDialogComponent, CalculatedFieldDebugDialogComponent, CalculatedFieldDebugDialogData,
CalculatedFieldDialogComponent, CalculatedFieldDialogComponent,
CalculatedFieldScriptTestDialogComponent CalculatedFieldDialogData,
CalculatedFieldScriptTestDialogComponent,
CalculatedFieldTestScriptDialogData
} from './components/public-api'; } from './components/public-api';
import { ImportExportService } from '@shared/import-export/import-export.service'; import { ImportExportService } from '@shared/import-export/import-export.service';
import { isObject } from '@core/utils'; import { isObject } from '@core/utils';
import { EntityDebugSettingsService } from '@home/components/entity/debug/entity-debug-settings.service';
import { DatePipe } from '@angular/common'; import { DatePipe } from '@angular/common';
export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedField, PageLink> { export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedField> {
// TODO: [Calculated Fields] remove hardcode when BE variable implemented
readonly calculatedFieldsDebugPerTenantLimitsConfiguration = readonly calculatedFieldsDebugPerTenantLimitsConfiguration =
getCurrentAuthState(this.store)['calculatedFieldsDebugPerTenantLimitsConfiguration'] || '1:1'; getCurrentAuthState(this.store)['calculatedFieldsDebugPerTenantLimitsConfiguration'] || '1:1';
readonly maxDebugModeDuration = getCurrentAuthState(this.store).maxDebugModeDurationMinutes * MINUTE; readonly maxDebugModeDuration = getCurrentAuthState(this.store).maxDebugModeDurationMinutes * MINUTE;
@ -78,12 +74,11 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
private datePipe: DatePipe, private datePipe: DatePipe,
public entityId: EntityId = null, public entityId: EntityId = null,
private store: Store<AppState>, private store: Store<AppState>,
private durationLeft: DurationLeftPipe,
private popoverService: TbPopoverService,
private destroyRef: DestroyRef, private destroyRef: DestroyRef,
private renderer: Renderer2, private renderer: Renderer2,
public entityName: string, public entityName: string,
private importExportService: ImportExportService private importExportService: ImportExportService,
private entityDebugSettingsService: EntityDebugSettingsService,
) { ) {
super(); super();
this.tableTitle = this.translate.instant('entity.type-calculated-fields'); this.tableTitle = this.translate.instant('entity.type-calculated-fields');
@ -147,10 +142,10 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
}, },
{ {
name: '', name: '',
nameFunction: entity => this.getDebugConfigLabel(entity?.debugSettings), nameFunction: entity => this.entityDebugSettingsService.getDebugConfigLabel(entity?.debugSettings),
icon: 'mdi:bug', icon: 'mdi:bug',
isEnabled: () => true, isEnabled: () => true,
iconFunction: ({ debugSettings }) => this.isDebugActive(debugSettings?.allEnabledUntil) || debugSettings?.failuresEnabled ? 'mdi:bug' : 'mdi:bug-outline', iconFunction: ({ debugSettings }) => this.entityDebugSettingsService.isDebugActive(debugSettings?.allEnabledUntil) || debugSettings?.failuresEnabled ? 'mdi:bug' : 'mdi:bug-outline',
onAction: ($event, entity) => this.onOpenDebugConfig($event, entity), onAction: ($event, entity) => this.onOpenDebugConfig($event, entity),
}, },
{ {
@ -180,30 +175,26 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
...this.additionalDebugActionConfig, ...this.additionalDebugActionConfig,
action: () => this.openDebugEventsDialog(calculatedField) action: () => this.openDebugEventsDialog(calculatedField)
}; };
const { viewContainerRef } = this.getTable();
if ($event) { if ($event) {
$event.stopPropagation(); $event.stopPropagation();
} }
const trigger = $event.target as Element;
if (this.popoverService.hasPopover(trigger)) { const { viewContainerRef, renderer } = this.entityDebugSettingsService;
this.popoverService.hidePopover(trigger); if (!viewContainerRef || !renderer) {
} else { this.entityDebugSettingsService.viewContainerRef = this.getTable().viewContainerRef;
const debugStrategyPopover = this.popoverService.displayPopover(trigger, this.renderer, this.entityDebugSettingsService.renderer = this.renderer;
viewContainerRef, EntityDebugSettingsPanelComponent, 'bottom', true, null,
{
debugLimitsConfiguration: this.calculatedFieldsDebugPerTenantLimitsConfiguration,
maxDebugModeDuration: this.maxDebugModeDuration,
entityLabel: this.translate.instant('debug-settings.calculated-field'),
additionalActionConfig,
...debugSettings
},
{},
{}, {}, true);
debugStrategyPopover.tbComponentRef.instance.onSettingsApplied.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((settings: EntityDebugSettings) => {
this.onDebugConfigChanged(id.id, settings);
debugStrategyPopover.hide();
});
} }
this.entityDebugSettingsService.openDebugStrategyPanel({
debugSettings,
debugConfig: {
debugLimitsConfiguration: this.calculatedFieldsDebugPerTenantLimitsConfiguration,
maxDebugModeDuration: this.maxDebugModeDuration,
entityLabel: this.translate.instant('debug-settings.calculated-field'),
additionalActionConfig,
},
onSettingsAppliedFn: settings => this.onDebugConfigChanged(id.id, settings)
}, $event.target as Element);
} }
private editCalculatedField(calculatedField: CalculatedField, isDirty = false): void { private editCalculatedField(calculatedField: CalculatedField, isDirty = false): void {
@ -258,25 +249,18 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
} }
private importCalculatedField(): void { private importCalculatedField(): void {
this.importExportService.importCalculatedField(this.entityId) this.importExportService.openCalculatedFieldImportDialog()
.pipe(filter(Boolean), takeUntilDestroyed(this.destroyRef)) .pipe(
filter(Boolean),
switchMap(calculatedField => this.getCalculatedFieldDialog(calculatedField, 'action.add')),
filter(Boolean),
switchMap(calculatedField => this.calculatedFieldsService.saveCalculatedField(calculatedField)),
filter(Boolean),
takeUntilDestroyed(this.destroyRef)
)
.subscribe(() => this.updateData()); .subscribe(() => this.updateData());
} }
private getDebugConfigLabel(debugSettings: EntityDebugSettings): string {
const isDebugActive = this.isDebugActive(debugSettings?.allEnabledUntil);
if (!isDebugActive) {
return debugSettings?.failuresEnabled ? this.translate.instant('debug-settings.failures') : this.translate.instant('common.disabled');
} else {
return this.durationLeft.transform(debugSettings?.allEnabledUntil);
}
}
private isDebugActive(allEnabledUntil: number): boolean {
return allEnabledUntil > new Date().getTime();
}
private onDebugConfigChanged(id: string, debugSettings: EntityDebugSettings): void { private onDebugConfigChanged(id: string, debugSettings: EntityDebugSettings): void {
this.calculatedFieldsService.getCalculatedFieldById(id).pipe( this.calculatedFieldsService.getCalculatedFieldById(id).pipe(
switchMap(field => this.calculatedFieldsService.saveCalculatedField({ ...field, debugSettings })), switchMap(field => this.calculatedFieldsService.saveCalculatedField({ ...field, debugSettings })),

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save