Browse Source

Merge remote-tracking branch 'origin/master' into feature/user-security-info

pull/11671/head
ViacheslavKlimov 2 years ago
parent
commit
87894cfcf4
  1. 2
      application/pom.xml
  2. 15
      application/src/main/data/json/system/scada_symbols/bottom-flow-meter.svg
  3. 48
      application/src/main/data/json/system/scada_symbols/bottom-right-elbow-pipe.svg
  4. 17
      application/src/main/data/json/system/scada_symbols/bottom-tee-pipe.svg
  5. 10
      application/src/main/data/json/system/scada_symbols/centrifugal-pump.svg
  6. 22
      application/src/main/data/json/system/scada_symbols/cross-pipe.svg
  7. 17
      application/src/main/data/json/system/scada_symbols/cylindrical-tank.svg
  8. 11
      application/src/main/data/json/system/scada_symbols/elevated-tank.svg
  9. 10
      application/src/main/data/json/system/scada_symbols/horizontal-ball-valve.svg
  10. 15
      application/src/main/data/json/system/scada_symbols/horizontal-inline-flow-meter.svg
  11. 11
      application/src/main/data/json/system/scada_symbols/horizontal-pipe.svg
  12. 17
      application/src/main/data/json/system/scada_symbols/horizontal-tank.svg
  13. 10
      application/src/main/data/json/system/scada_symbols/horizontal-wheel-valve.svg
  14. 17
      application/src/main/data/json/system/scada_symbols/large-cylindrical-tank.svg
  15. 17
      application/src/main/data/json/system/scada_symbols/large-stand-cylindrical-tank.svg
  16. 17
      application/src/main/data/json/system/scada_symbols/large-stand-vertical-tank.svg
  17. 17
      application/src/main/data/json/system/scada_symbols/large-vertical-tank.svg
  18. 8
      application/src/main/data/json/system/scada_symbols/leak-sensor.svg
  19. 6
      application/src/main/data/json/system/scada_symbols/left-analog-water-level-meter.svg
  20. 58
      application/src/main/data/json/system/scada_symbols/left-bottom-elbow-pipe.svg
  21. 15
      application/src/main/data/json/system/scada_symbols/left-flow-meter.svg
  22. 12
      application/src/main/data/json/system/scada_symbols/left-heat-pump.svg
  23. 2
      application/src/main/data/json/system/scada_symbols/left-motor-pump.svg
  24. 17
      application/src/main/data/json/system/scada_symbols/left-tee-pipe.svg
  25. 48
      application/src/main/data/json/system/scada_symbols/left-top-elbow-pipe.svg
  26. 11
      application/src/main/data/json/system/scada_symbols/long-horizontal-pipe.svg
  27. 11
      application/src/main/data/json/system/scada_symbols/long-vertical-pipe.svg
  28. 15
      application/src/main/data/json/system/scada_symbols/pool.svg
  29. 6
      application/src/main/data/json/system/scada_symbols/right-analog-water-level-meter.svg
  30. 15
      application/src/main/data/json/system/scada_symbols/right-flow-meter.svg
  31. 12
      application/src/main/data/json/system/scada_symbols/right-heat-pump.svg
  32. 2
      application/src/main/data/json/system/scada_symbols/right-motor-pump.svg
  33. 17
      application/src/main/data/json/system/scada_symbols/right-tee-pipe.svg
  34. 14
      application/src/main/data/json/system/scada_symbols/sand-filter.svg
  35. 2
      application/src/main/data/json/system/scada_symbols/small-left-motor-pump.svg
  36. 8
      application/src/main/data/json/system/scada_symbols/small-right-motor-pump.svg
  37. 17
      application/src/main/data/json/system/scada_symbols/small-spherical-tank.svg
  38. 17
      application/src/main/data/json/system/scada_symbols/spherical-tank.svg
  39. 17
      application/src/main/data/json/system/scada_symbols/stand-cylindrical-tank.svg
  40. 17
      application/src/main/data/json/system/scada_symbols/stand-horizontal-tank.svg
  41. 17
      application/src/main/data/json/system/scada_symbols/stand-vertical-short-tank.svg
  42. 17
      application/src/main/data/json/system/scada_symbols/stand-vertical-tank.svg
  43. 15
      application/src/main/data/json/system/scada_symbols/top-flow-meter.svg
  44. 50
      application/src/main/data/json/system/scada_symbols/top-right-elbow-pipe.svg
  45. 17
      application/src/main/data/json/system/scada_symbols/top-tee-pipe.svg
  46. 10
      application/src/main/data/json/system/scada_symbols/vertical-ball-valve.svg
  47. 15
      application/src/main/data/json/system/scada_symbols/vertical-inline-flow-meter.svg
  48. 11
      application/src/main/data/json/system/scada_symbols/vertical-pipe.svg
  49. 17
      application/src/main/data/json/system/scada_symbols/vertical-short-tank.svg
  50. 17
      application/src/main/data/json/system/scada_symbols/vertical-tank.svg
  51. 4
      application/src/main/data/json/system/scada_symbols/vertical-wheel-valve.svg
  52. 12
      application/src/main/data/json/system/scada_symbols/waterstop.svg
  53. 2
      application/src/main/data/json/system/widget_types/gateway_connectors.json
  54. 2
      application/src/main/data/json/system/widget_types/rpc_remote_shell.json
  55. 2
      application/src/main/data/json/system/widget_types/scada_symbol.json
  56. 24
      application/src/main/data/json/tenant/dashboards/gateways.json
  57. 0
      application/src/main/data/upgrade/3.8.0/schema_update.sql
  58. 8
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  59. 46
      application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java
  60. 4
      application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java
  61. 30
      application/src/main/java/org/thingsboard/server/service/housekeeper/processor/AlarmsUnassignTaskProcessor.java
  62. 6
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  63. 2
      application/src/main/resources/thingsboard.yml
  64. 4
      application/src/test/java/org/thingsboard/server/controller/ImageControllerTest.java
  65. 7
      application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java
  66. 37
      application/src/test/java/org/thingsboard/server/service/housekeeper/HousekeeperServiceTest.java
  67. 30
      application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java
  68. 1
      application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java
  69. 19
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java
  70. 7
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java
  71. 2
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationObserveTest.java
  72. 19
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java
  73. 502
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2MIntegrationObserveCompositeTest.java
  74. 20
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java
  75. 315
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java
  76. 2
      common/actor/pom.xml
  77. 2
      common/cache/pom.xml
  78. 2
      common/cluster-api/pom.xml
  79. 2
      common/coap-server/pom.xml
  80. 2
      common/dao-api/pom.xml
  81. 3
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
  82. 2
      common/data/pom.xml
  83. 20
      common/data/src/main/java/org/thingsboard/server/common/data/housekeeper/AlarmsUnassignHousekeeperTask.java
  84. 2
      common/edge-api/pom.xml
  85. 2
      common/message/pom.xml
  86. 2
      common/pom.xml
  87. 2
      common/proto/pom.xml
  88. 2
      common/queue/pom.xml
  89. 2
      common/script/pom.xml
  90. 2
      common/script/remote-js-client/pom.xml
  91. 2
      common/script/script-api/pom.xml
  92. 2
      common/stats/pom.xml
  93. 2
      common/transport/coap/pom.xml
  94. 2
      common/transport/http/pom.xml
  95. 2
      common/transport/lwm2m/pom.xml
  96. 201
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java
  97. 166
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbInMemoryRegistrationStore.java
  98. 135
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java
  99. 22
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java
  100. 36
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java

2
application/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.7.1-SNAPSHOT</version> <version>3.9.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>application</artifactId> <artifactId>application</artifactId>

15
application/src/main/data/json/system/scada_symbols/bottom-flow-meter.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="400" fill="none" version="1.1" viewBox="0 0 400 400"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="400" fill="none" version="1.1" viewBox="0 0 400 400"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Bottom flow meter", "title": "Bottom flow meter",
"description": "Bottom flow meter component used to display flow related value and render various states. Includes pipe fluid and leak visualizations.", "description": "Bottom flow meter component used to display flow related value and render various states. Includes pipe fluid and leak visualizations.",
"searchTags": [ "searchTags": [
@ -20,7 +19,7 @@
}, },
{ {
"tag": "border", "tag": "border",
"stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}", "stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (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}",
"actions": { "actions": {
"click": { "click": {
"actionFunction": "ctx.api.callAction(event, 'displayClick');" "actionFunction": "ctx.api.callAction(event, 'displayClick');"
@ -38,7 +37,7 @@
}, },
{ {
"tag": "fluid", "tag": "fluid",
"stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill');\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(ctx.values.flowAnimationSpeed);\n }\n} else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}", "stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill').first();\n\nvar fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(ctx.values.flowAnimationSpeed);\n }\n }\n} else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}",
"actions": null "actions": null
}, },
{ {
@ -733,7 +732,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><mask id="path-7-inside-1_1253_89545" fill="white"> }]]></tb:metadata>
<mask id="path-7-inside-1_1253_89545" fill="white">
<path d="m325 125c0 69.036-55.964 125-125 125s-125-55.964-125-125 55.964-125 125-125 125 55.964 125 125zm-236.01 0c0 61.306 49.699 111 111.01 111s111-49.699 111-111c0-61.306-49.699-111.01-111-111.01s-111.01 49.699-111.01 111.01z"/> <path d="m325 125c0 69.036-55.964 125-125 125s-125-55.964-125-125 55.964-125 125-125 125 55.964 125 125zm-236.01 0c0 61.306 49.699 111 111.01 111s111-49.699 111-111c0-61.306-49.699-111.01-111-111.01s-111.01 49.699-111.01 111.01z"/>
</mask><defs> </mask><defs>
<linearGradient id="paint2_linear_1253_89545" x1="155.2" x2="177.6" y1="61" y2="85.4" gradientTransform="translate(0,150)" gradientUnits="userSpaceOnUse"> <linearGradient id="paint2_linear_1253_89545" x1="155.2" x2="177.6" y1="61" y2="85.4" gradientTransform="translate(0,150)" gradientUnits="userSpaceOnUse">
@ -750,7 +750,7 @@
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_1281_42354"/> <feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_1281_42354"/>
<feBlend in="SourceGraphic" in2="effect1_backgroundBlur_1281_42354" result="shape"/> <feBlend in="SourceGraphic" in2="effect1_backgroundBlur_1281_42354" result="shape"/>
</filter> </filter>
<pattern id="liquid" width="172" height="72" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse"> <pattern id="baseLiquid" width="172" height="72" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/> <circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/> <circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/> <circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
@ -781,6 +781,7 @@
<circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/> <circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/>
<path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/> <path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/>
</pattern> </pattern>
<pattern id="liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#baseLiquid)"/></pattern>
<linearGradient id="paint28_linear_1182_32781-5" x1="19.316" x2="21" y1="8.2632" y2="23" gradientTransform="translate(-36,6)" gradientUnits="userSpaceOnUse"> <linearGradient id="paint28_linear_1182_32781-5" x1="19.316" x2="21" y1="8.2632" y2="23" gradientTransform="translate(-36,6)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/> <stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".15" offset="1"/> <stop stop-opacity=".15" offset="1"/>
@ -925,7 +926,7 @@
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_1281_42354"/> <feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_1281_42354"/>
<feBlend in="SourceGraphic" in2="effect1_backgroundBlur_1281_42354" result="shape"/> <feBlend in="SourceGraphic" in2="effect1_backgroundBlur_1281_42354" result="shape"/>
</filter> </filter>
</defs><rect x="14" y="64" width="372" height="72" fill="#fff" tb:tag="pipe-background"/><rect x="14" y="64" width="372" height="72" fill="url(#paint0_linear_1310_41908)" style="fill:url(#paint0_linear_1310_41908)"/><rect x="15.5" y="65.5" width="369" height="69" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="14" y="64" width="372" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect x="14" y="64" width="372" height="72" fill="url(#liquid)" stroke-width="0" style="display: none;" tb:tag="fluid"/><path d="m164 164v-28.421l36-31.579 36 31.579v28.421z" fill="#fff" tb:tag="pipe-background"/><path d="m164 164v-28.421l36-31.579 36 31.579v28.421z" fill="url(#paint1_linear_1310_41908)" style="fill:url(#paint1_linear_1310_41908)"/><path d="m165.5 162.5v-26.242l34.5-30.263 34.5 30.263v26.242z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="387.5" y="51.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><rect x="1.5" y="51.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><path transform="translate(0,150)" d="m325 125c0 69.036-55.964 125-125 125s-125-55.964-125-125 55.964-125 125-125 125 55.964 125 125zm-236.01 0c0 61.306 49.699 111 111.01 111s111-49.699 111-111c0-61.306-49.699-111.01-111-111.01s-111.01 49.699-111.01 111.01z" fill="#4a4848" mask="url(#path-7-inside-1_1253_89545)" stroke="#727171" stroke-width="6" tb:tag="border"/><circle cx="200" cy="275" r="111" fill="#fff" tb:tag="background"/><rect x="119.5" y="255.5" width="161" height="39" rx="3.8571" fill="#4a4848" fill-opacity=".06" tb:tag="border"/><path d="m123.36 254c-2.9419 0-5.3574 2.4155-5.3574 5.3574v31.285c0 2.9419 2.4155 5.3574 5.3574 5.3574h153.29c2.9419 0 5.3574-2.4155 5.3574-5.3574v-31.285c0-2.9419-2.4155-5.3574-5.3574-5.3574zm0 3h153.29c1.3318 0 2.3574 1.0256 2.3574 2.3574v31.285c0 1.3318-1.0256 2.3574-2.3574 2.3574h-153.29c-1.3318 0-2.3574-1.0256-2.3574-2.3574v-31.285c0-1.3318 1.0256-2.3574 2.3574-2.3574z" fill="#4a4848" tb:tag="border"/><circle cx="166" cy="223" r="16" fill="url(#paint2_linear_1253_89545)" style="fill:url(#paint2_linear_1253_89545)"/><circle cx="166" cy="223" r="15.5" stroke="#000" stroke-opacity=".12"/><circle cx="234" cy="223" r="16" fill="url(#paint3_linear_1253_89545)" style="fill:url(#paint3_linear_1253_89545)"/><circle cx="234" cy="223" r="15.5" stroke="#000" stroke-opacity=".12"/><text x="198.96094" y="276.94531" dominant-baseline="middle" fill="#000000" fill-opacity=".87" text-anchor="middle" style="font-family:Roboto;font-size:32px;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-weight:500" tb:tag="value" xml:space="preserve"><tspan>0</tspan></text><text x="199.47363" y="324.4729" dominant-baseline="middle" fill="#000000" fill-opacity=".87" text-anchor="middle" style="font-family:Roboto;font-size:22px;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-weight:500" tb:tag="valueUnits" xml:space="preserve"><tspan>m³/hr</tspan></text><g transform="translate(0 150)" style="display: none;" tb:tag="broken"> </defs><rect x="14" y="64" width="372" height="72" fill="#fff" tb:tag="pipe-background"/><rect x="14" y="64" width="372" height="72" fill="url(#paint0_linear_1310_41908)" style="fill:url(#paint0_linear_1310_41908)"/><rect x="15.5" y="65.5" width="369" height="69" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="14" y="64" width="372" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect x="14" y="64" width="372" height="72" fill="url(#liquid)" stroke-width="0" style="display: none;" tb:tag="fluid"/><path d="m164 164v-28.421l36-31.579 36 31.579v28.421z" fill="#fff" tb:tag="pipe-background"/><path d="m164 164v-28.421l36-31.579 36 31.579v28.421z" fill="url(#paint1_linear_1310_41908)" style="fill:url(#paint1_linear_1310_41908)"/><path d="m165.5 162.5v-26.242l34.5-30.263 34.5 30.263v26.242z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="387.5" y="51.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><rect x="1.5" y="51.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><path transform="translate(0,150)" d="m325 125c0 69.036-55.964 125-125 125s-125-55.964-125-125 55.964-125 125-125 125 55.964 125 125zm-236.01 0c0 61.306 49.699 111 111.01 111s111-49.699 111-111c0-61.306-49.699-111.01-111-111.01s-111.01 49.699-111.01 111.01z" fill="#4a4848" mask="url(#path-7-inside-1_1253_89545)" stroke="#727171" stroke-width="6" tb:tag="border"/><circle cx="200" cy="275" r="111" fill="#fff" tb:tag="background"/><rect x="119.5" y="255.5" width="161" height="39" rx="3.8571" fill="#4a4848" fill-opacity=".06" tb:tag="border"/><path d="m123.36 254c-2.9419 0-5.3574 2.4155-5.3574 5.3574v31.285c0 2.9419 2.4155 5.3574 5.3574 5.3574h153.29c2.9419 0 5.3574-2.4155 5.3574-5.3574v-31.285c0-2.9419-2.4155-5.3574-5.3574-5.3574zm0 3h153.29c1.3318 0 2.3574 1.0256 2.3574 2.3574v31.285c0 1.3318-1.0256 2.3574-2.3574 2.3574h-153.29c-1.3318 0-2.3574-1.0256-2.3574-2.3574v-31.285c0-1.3318 1.0256-2.3574 2.3574-2.3574z" fill="#4a4848" tb:tag="border"/><circle cx="166" cy="223" r="16" fill="url(#paint2_linear_1253_89545)" style="fill:url(#paint2_linear_1253_89545)"/><circle cx="166" cy="223" r="15.5" stroke="#000" stroke-opacity=".12"/><circle cx="234" cy="223" r="16" fill="url(#paint3_linear_1253_89545)" style="fill:url(#paint3_linear_1253_89545)"/><circle cx="234" cy="223" r="15.5" stroke="#000" stroke-opacity=".12"/><text x="198.96094" y="276.94531" fill="#000000" fill-opacity=".87" style="font-family:Roboto;font-size:32px;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-weight:500" tb:tag="value" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">0</tspan></text><text x="199.47363" y="324.4729" fill="#000000" fill-opacity=".87" style="font-family:Roboto;font-size:22px;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-weight:500" tb:tag="valueUnits" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">m³/hr</tspan></text><g transform="translate(0 150)" style="display: none;" tb:tag="broken">
<g filter="url(#filter0_b_1281_42354)" style="filter: url(&quot;#filter0_b_1281_42354-3&quot;);"> <g filter="url(#filter0_b_1281_42354)" style="filter: url(&quot;#filter0_b_1281_42354-3&quot;);">
<circle cx="200" cy="125" r="111" fill="#000" fill-opacity=".24" style=""/> <circle cx="200" cy="125" r="111" fill="#000" fill-opacity=".24" style=""/>
</g> </g>

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

48
application/src/main/data/json/system/scada_symbols/bottom-right-elbow-pipe.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Bottom right elbow pipe", "title": "Bottom right elbow pipe",
"description": "Bottom right elbow pipe with fluid and leak visualizations.", "description": "Bottom right elbow pipe with fluid and leak visualizations.",
"searchTags": [ "searchTags": [
@ -8,7 +7,7 @@
], ],
"widgetSizeX": 1, "widgetSizeX": 1,
"widgetSizeY": 1, "widgetSizeY": 1,
"stateRenderFunction": "var centerLiquidPattern = prepareLiquidPattern('center-fluid-background');\nvar horizontalLiquidPattern = prepareLiquidPattern('horizontal-fluid');\nvar verticalLiquidPattern = prepareLiquidPattern('vertical-fluid');\n\nvar fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\nvar flowAnimationSpeed = ctx.values.flowAnimationSpeed;\n \nif (horizontalLiquidPattern) {\n updateLiquidPatternAnimation(horizontalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (verticalLiquidPattern) {\n updateLiquidPatternAnimation(verticalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (centerLiquidPattern) {\n updateLiquidPatternAnimation(centerLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, true);\n}\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill');\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, fluid, flow, flowDirection, flowAnimationSpeed, center) {\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection, center);\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection, center);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(flowAnimationSpeed);\n }\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse, center) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n if (center) {\n var delta = deltaX * 1.17 * Math.cos(45*Math.PI/180);\n liquidPattern.animate(1000).ease('-').relative(-delta, delta).loop();\n } else {\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}\n\n", "stateRenderFunction": "var centerLiquidPattern = prepareLiquidPattern('center-fluid-background');\nvar horizontalLiquidPattern = prepareLiquidPattern('horizontal-fluid');\nvar verticalLiquidPattern = prepareLiquidPattern('vertical-fluid');\n\nvar fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\nvar flowAnimationSpeed = ctx.values.flowAnimationSpeed;\n \nif (horizontalLiquidPattern) {\n updateLiquidPatternAnimation(horizontalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (verticalLiquidPattern) {\n updateLiquidPatternAnimation(verticalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (centerLiquidPattern) {\n updateLiquidPatternAnimation(centerLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, true);\n}\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill').first();\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, fluid, flow, flowDirection, flowAnimationSpeed, center) {\n \n var fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n liquidPattern.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection, center);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection, center);\n }\n if (flow && fluidAnimation) {\n fluidAnimation.speed(flowAnimationSpeed);\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse, center) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n if (center) {\n var duration = 1000 * 1.17;\n return ctx.api.cssAnimate(liquidPattern, duration).relative(deltaX, 0).loop();\n } else {\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n }\n}",
"tags": [ "tags": [
{ {
"tag": "center-fluid", "tag": "center-fluid",
@ -251,7 +250,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><g transform="rotate(-90,100,100)" clip-path="url(#clip0_1245_66459)"> }]]></tb:metadata>
<g transform="rotate(-90,100,100)" clip-path="url(#clip0_1245_66459)">
<rect x="14" y="64" width="50" height="72" fill="#fff" tb:tag="pipe-background"/> <rect x="14" y="64" width="50" height="72" fill="#fff" tb:tag="pipe-background"/>
<rect x="14" y="64" width="50" height="72" fill="url(#paint0_linear_1245_66459)" style="fill:url(#paint0_linear_1245_66459)"/> <rect x="14" y="64" width="50" height="72" fill="url(#paint0_linear_1245_66459)" style="fill:url(#paint0_linear_1245_66459)"/>
<rect x="15.5" y="65.5" width="47" height="69" stroke="#000" stroke-opacity=".12" stroke-width="3"/> <rect x="15.5" y="65.5" width="47" height="69" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
@ -264,7 +264,7 @@
<path d="m64 64s30.518 1.7177 50.4 21.6c19.882 19.882 21.6 50.4 21.6 50.4h-72z" fill="url(#paint2_linear_1245_66459)" style="fill:url(#paint2_linear_1245_66459)"/> <path d="m64 64s30.518 1.7177 50.4 21.6c19.882 19.882 21.6 50.4 21.6 50.4h-72z" fill="url(#paint2_linear_1245_66459)" style="fill:url(#paint2_linear_1245_66459)"/>
<path d="m65.5 134.5v-68.865c0.8334 0.0861 1.9717 0.2211 3.3584 0.4273 3.1196 0.464 7.4889 1.2873 12.47 2.7228 9.9828 2.8767 22.316 8.1809 32.01 17.875 9.695 9.6942 14.999 22.027 17.875 32.01 1.436 4.982 2.259 9.351 2.723 12.471 0.206 1.386 0.341 2.525 0.428 3.358z" stroke="#000" stroke-opacity=".12" stroke-width="3"/> <path d="m65.5 134.5v-68.865c0.8334 0.0861 1.9717 0.2211 3.3584 0.4273 3.1196 0.464 7.4889 1.2873 12.47 2.7228 9.9828 2.8767 22.316 8.1809 32.01 17.875 9.695 9.6942 14.999 22.027 17.875 32.01 1.436 4.982 2.259 9.351 2.723 12.471 0.206 1.386 0.341 2.525 0.428 3.358z" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
</g><defs> </g><defs>
<pattern id="horizontal-liquid" width="172" height="72" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse"> <pattern id="liquid" width="172" height="72" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/> <circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/> <circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/> <circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
@ -415,37 +415,6 @@
<stop stop-color="#727171" offset=".89138"/> <stop stop-color="#727171" offset=".89138"/>
<stop stop-color="#727171" offset="1"/> <stop stop-color="#727171" offset="1"/>
</linearGradient> </linearGradient>
<pattern id="vertical-liquid" width="172" height="72" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
<circle transform="rotate(-90)" cx="-58" cy="34" r="8" fill="url(#paint31_linear_1182_32781-4)"/>
<circle transform="rotate(-90)" cx="-58" cy="155" r="8" fill="url(#paint32_linear_1182_32781-3)"/>
<circle transform="rotate(-90)" cx="-26" cy="33" r="5" fill="url(#paint33_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-26" cy="162" r="5" fill="url(#paint34_linear_1182_32781-4)"/>
<circle transform="rotate(-90)" cx="-37" cy="5" r="5" fill="url(#paint35_linear_1182_32781-6)"/>
<circle transform="rotate(-90)" cx="-8" cy="94" r="4" fill="url(#paint36_linear_1182_32781-9)"/>
<circle transform="rotate(-90)" cx="-60" cy="72" r="4" fill="url(#paint37_linear_1182_32781-4)"/>
<circle transform="rotate(-90)" cx="-37" cy="112" r="5" fill="url(#paint38_linear_1182_32781-2)"/>
<circle transform="rotate(-90)" cx="-39" cy="59" r="5" fill="url(#paint39_linear_1182_32781-2)"/>
<circle transform="rotate(-90)" cx="-62" cy="115" r="5" fill="url(#paint40_linear_1182_32781-6)"/>
<circle transform="rotate(-90)" cx="-42" cy="139" r="5" fill="url(#paint41_linear_1182_32781-4)"/>
<circle transform="rotate(-90)" cx="-21" cy="76" r="5" fill="url(#paint42_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-50.5" cy="126.5" r="2.5" fill="url(#paint43_linear_1182_32781-2)"/>
<circle transform="rotate(-90)" cx="-46.5" cy="169.5" r="2.5" fill="url(#paint44_linear_1182_32781-8)"/>
<circle transform="rotate(-90)" cx="-9.5" cy="57.5" r="2.5" fill="url(#paint45_linear_1182_32781-8)"/>
<circle transform="rotate(-90)" cx="-35.5" cy="96.5" r="2.5" fill="url(#paint46_linear_1182_32781-9)"/>
<circle transform="rotate(-90)" cx="-23.5" cy="91.5" r="2.5" fill="url(#paint47_linear_1182_32781-2)"/>
<circle transform="rotate(-90)" cx="-40.5" cy="22.5" r="2.5" fill="url(#paint48_linear_1182_32781-88)"/>
<circle transform="rotate(-90)" cx="-23.5" cy="124.5" r="2.5" fill="url(#paint49_linear_1182_32781-8)"/>
<circle transform="rotate(-90)" cx="-47.5" cy="86.5" r="2.5" fill="url(#paint50_linear_1182_32781-6)"/>
<circle transform="rotate(-90)" cx="-21.5" cy="51.5" r="2.5" fill="url(#paint51_linear_1182_32781-8)"/>
<circle transform="rotate(-90)" cx="-51.5" cy="48.5" r="2.5" fill="url(#paint52_linear_1182_32781-3)"/>
<circle transform="rotate(-90)" cx="-64" cy="14" r="4" fill="url(#paint53_linear_1182_32781-83)"/>
<circle transform="rotate(-90)" cx="-64" cy="135" r="4" fill="url(#paint54_linear_1182_32781-33)"/>
<circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/>
<path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/>
</pattern>
<clipPath id="clip0_1245_66459"> <clipPath id="clip0_1245_66459">
<rect width="200" height="200" fill="#fff"/> <rect width="200" height="200" fill="#fff"/>
</clipPath> </clipPath>
@ -530,7 +499,12 @@
<clipPath id="clipPath4208"> <clipPath id="clipPath4208">
<rect width="200" height="200" fill="#fff"/> <rect width="200" height="200" fill="#fff"/>
</clipPath> </clipPath>
<pattern id="center-liquid" patternTransform="scale(1.17) rotate(-45)" patternUnits="userSpaceOnUse" xlink:href="#horizontal-liquid"/> <pattern id="base-horizontal-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="horizontal-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect x="-172" width="688" height="72" fill="url(#base-horizontal-liquid)" stroke-width="0"/></pattern>
<pattern id="base-vertical-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="vertical-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect x="-172" width="688" height="72" fill="url(#base-vertical-liquid)" stroke-width="0"/></pattern>
<pattern id="base-center-liquid" width="172" height="72" patternTransform="translate(15)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="center-liquid" width="172" height="72" patternTransform="scale(1.17) rotate(135)" patternUnits="userSpaceOnUse"><rect x="-172" width="688" height="72" fill="url(#base-center-liquid)" stroke-width="0"/></pattern>
</defs><rect x="136" y="64" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect transform="rotate(90)" x="136" y="-136" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect x="136" y="64" width="50" height="72" fill="url(#horizontal-liquid)" stroke-width="0" style="display: none;" tb:tag="horizontal-fluid"/><rect transform="rotate(-90)" x="-186" y="64" width="50" height="72" fill="url(#vertical-liquid)" stroke-width="0" style="display: none;" tb:tag="vertical-fluid"/><path d="m136.05 136-72.05 0.1c0.09-0.87135 0.23117-2.1615 0.44675-3.6113 0.48513-3.2616 1.3459-7.8299 2.8468-13.038 3.0077-10.437 8.5533-23.332 18.689-33.468 10.136-10.136 23.03-15.682 33.467-18.689 5.2088-1.5014 9.7767-2.3619 13.039-2.847 1.4491-0.21538 2.6909-0.35652 3.5618-0.44748z" fill="#1ec1f4" stroke="#000" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><g transform="matrix(-.99929 0 0 -.99919 199.91 199.9)" style="display: none;" tb:tag="center-fluid"> </defs><rect x="136" y="64" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect transform="rotate(90)" x="136" y="-136" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect x="136" y="64" width="50" height="72" fill="url(#horizontal-liquid)" stroke-width="0" style="display: none;" tb:tag="horizontal-fluid"/><rect transform="rotate(-90)" x="-186" y="64" width="50" height="72" fill="url(#vertical-liquid)" stroke-width="0" style="display: none;" tb:tag="vertical-fluid"/><path d="m136.05 136-72.05 0.1c0.09-0.87135 0.23117-2.1615 0.44675-3.6113 0.48513-3.2616 1.3459-7.8299 2.8468-13.038 3.0077-10.437 8.5533-23.332 18.689-33.468 10.136-10.136 23.03-15.682 33.467-18.689 5.2088-1.5014 9.7767-2.3619 13.039-2.847 1.4491-0.21538 2.6909-0.35652 3.5618-0.44748z" fill="#1ec1f4" stroke="#000" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><g transform="matrix(-.99929 0 0 -.99919 199.91 199.9)" style="display: none;" tb:tag="center-fluid">
<path d="m63.956 63.95 72.051-0.10008c-0.0901 0.87205-0.23133 2.1632-0.44707 3.6142-0.48547 3.2643-1.3469 7.8362-2.8488 13.048-3.0098 10.446-8.5594 23.351-18.702 33.495-10.143 10.145-23.046 15.695-33.491 18.704-5.2125 1.5026-9.7836 2.3638-13.048 2.8493-1.4501 0.21555-2.7428 0.3568-3.6144 0.44784z" fill="url(#center-liquid)" stroke="#000" stroke-width="0" tb:tag="center-fluid-background" style=""/> <path d="m63.956 63.95 72.051-0.10008c-0.0901 0.87205-0.23133 2.1632-0.44707 3.6142-0.48547 3.2643-1.3469 7.8362-2.8488 13.048-3.0098 10.446-8.5594 23.351-18.702 33.495-10.143 10.145-23.046 15.695-33.491 18.704-5.2125 1.5026-9.7836 2.3638-13.048 2.8493-1.4501 0.21555-2.7428 0.3568-3.6144 0.44784z" fill="url(#center-liquid)" stroke="#000" stroke-width="0" tb:tag="center-fluid-background" style=""/>
<path d="m132.05 63.75s-0.0216 31.824-17.622 50.677c-17.142 17.939-50.576 17.628-50.576 17.628" fill="none" stroke="#727171" stroke-width="7.9" style=""/> <path d="m132.05 63.75s-0.0216 31.824-17.622 50.677c-17.142 17.939-50.576 17.628-50.576 17.628" fill="none" stroke="#727171" stroke-width="7.9" style=""/>

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 49 KiB

17
application/src/main/data/json/system/scada_symbols/bottom-tee-pipe.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Bottom tee pipe", "title": "Bottom tee pipe",
"description": "Bottom tee pipe with configurable left/right/bottom fluid and leak visualizations.", "description": "Bottom tee pipe with configurable left/right/bottom fluid and leak visualizations.",
"searchTags": [ "searchTags": [
@ -8,7 +7,7 @@
], ],
"widgetSizeX": 1, "widgetSizeX": 1,
"widgetSizeY": 1, "widgetSizeY": 1,
"stateRenderFunction": "var leftLiquidPattern = prepareLiquidPattern('left-fluid');\nvar rightLiquidPattern = prepareLiquidPattern('right-fluid');\nvar bottomLiquidPattern = prepareLiquidPattern('bottom-fluid');\n\nupdateLiquidPatternAnimation(leftLiquidPattern, 'left');\nupdateLiquidPatternAnimation(rightLiquidPattern, 'right');\nupdateLiquidPatternAnimation(bottomLiquidPattern, 'bottom');\n\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill');\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, prefix) {\n if (liquidPattern) {\n var fluid = ctx.values[prefix + 'Fluid'] && !ctx.values.leak;\n var flow = ctx.values[prefix + 'Flow'];\n var flowDirection = ctx.values[prefix + 'FlowDirection'];\n var flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(flowAnimationSpeed);\n }\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n}\n\n", "stateRenderFunction": "var leftLiquidPattern = prepareLiquidPattern('left-fluid');\nvar rightLiquidPattern = prepareLiquidPattern('right-fluid');\nvar bottomLiquidPattern = prepareLiquidPattern('bottom-fluid');\n\nupdateLiquidPatternAnimation(leftLiquidPattern, 'left');\nupdateLiquidPatternAnimation(rightLiquidPattern, 'right');\nupdateLiquidPatternAnimation(bottomLiquidPattern, 'bottom');\n\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill').first();\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, prefix) {\n if (liquidPattern) {\n var fluid = ctx.values[prefix + 'Fluid'] && !ctx.values.leak;\n var flow = ctx.values[prefix + 'Flow'];\n var flowDirection = ctx.values[prefix + 'FlowDirection'];\n var flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n var fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(flowAnimationSpeed);\n }\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}\n",
"tags": [ "tags": [
{ {
"tag": "bottom-fluid", "tag": "bottom-fluid",
@ -576,7 +575,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><g clip-path="url(#clip0_1245_66617)"> }]]></tb:metadata>
<g clip-path="url(#clip0_1245_66617)">
<path d="m14 64h172v72h-172z" fill="#fff" tb:tag="pipe-background"/> <path d="m14 64h172v72h-172z" fill="#fff" tb:tag="pipe-background"/>
<path d="m14 64h172v72h-172z" fill="url(#paint0_linear_1245_66617)" style="fill:url(#paint0_linear_1245_66617)"/> <path d="m14 64h172v72h-172z" fill="url(#paint0_linear_1245_66617)" style="fill:url(#paint0_linear_1245_66617)"/>
<path d="m15.5 65.5h169v69h-169z" stroke="#000" stroke-opacity=".12" stroke-width="3"/> <path d="m15.5 65.5h169v69h-169z" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
@ -738,9 +738,12 @@
<stop stop-color="#727171" offset=".89138"/> <stop stop-color="#727171" offset=".89138"/>
<stop stop-color="#727171" offset="1"/> <stop stop-color="#727171" offset="1"/>
</linearGradient> </linearGradient>
<pattern id="left-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/> <pattern id="base-left-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="right-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/> <pattern id="left-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#base-left-liquid)"/></pattern>
<pattern id="bottom-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/> <pattern id="base-right-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="right-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#base-right-liquid)"/></pattern>
<pattern id="base-bottom-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="bottom-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#base-bottom-liquid)"/></pattern>
<clipPath id="clip0_1245_66617"> <clipPath id="clip0_1245_66617">
<rect width="200" height="200" fill="#fff"/> <rect width="200" height="200" fill="#fff"/>
</clipPath> </clipPath>

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 58 KiB

10
application/src/main/data/json/system/scada_symbols/centrifugal-pump.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="400" fill="none" version="1.1" viewBox="0 0 400 400"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="400" fill="none" version="1.1" viewBox="0 0 400 400"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Centrifugal pump", "title": "Centrifugal pump",
"description": "Centrifugal pump with configurable connectors, running animation and various states.", "description": "Centrifugal pump with configurable connectors, running animation and various states.",
"searchTags": [ "searchTags": [
@ -11,12 +10,12 @@
"tags": [ "tags": [
{ {
"tag": "background", "tag": "background",
"stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}", "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (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}",
"actions": null "actions": null
}, },
{ {
"tag": "center", "tag": "center",
"stateRenderFunction": "var running = ctx.values.running;\nvar t = element.timeline();\nif (running) {\n if (!t.active()) {\n if (t.time()) {\n t.play();\n } else {\n ctx.api.animate(element, 2000).ease('-').rotate(360).loop();\n t = element.timeline();\n }\n }\n var speed = ctx.values.rotationAnimationSpeed;\n t.speed(speed);\n} else {\n t.pause();\n}\n", "stateRenderFunction": "var running = ctx.values.running;\nvar speed = ctx.values.rotationAnimationSpeed;\nvar centerRotate = ctx.api.cssAnimation(element);\nif (running) {\n if (!centerRotate) {\n centerRotate = ctx.api.cssAnimate(element, 2000)\n .rotate(360).loop().speed(speed);\n } else {\n centerRotate.speed(speed).play();\n }\n} else {\n if (centerRotate) {\n centerRotate.pause();\n }\n}\n",
"actions": null "actions": null
}, },
{ {
@ -415,7 +414,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><rect x="50" y="371" width="300" height="29" rx="7" fill="#fff"/><rect x="50" y="371" width="300" height="29" rx="7" fill="url(#paint0_linear_1481_75415)"/><rect x="51.5" y="372.5" width="297" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m103 274h194l32 100h-258l32-100z" fill="#fff"/><path d="m103 274h194l32 100h-258l32-100z" fill="url(#paint1_linear_1481_75415)"/><path d="m73.055 372.5 31.04-97h191.81l31.04 97h-253.89z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><g tb:tag="rightTopConnector"> }]]></tb:metadata>
<rect x="50" y="371" width="300" height="29" rx="7" fill="#fff"/><rect x="50" y="371" width="300" height="29" rx="7" fill="url(#paint0_linear_1481_75415)"/><rect x="51.5" y="372.5" width="297" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m103 274h194l32 100h-258l32-100z" fill="#fff"/><path d="m103 274h194l32 100h-258l32-100z" fill="url(#paint1_linear_1481_75415)"/><path d="m73.055 372.5 31.04-97h191.81l31.04 97h-253.89z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><g tb:tag="rightTopConnector">
<rect x="200" y="64" width="186" height="72" fill="#fff"/> <rect x="200" y="64" width="186" height="72" fill="#fff"/>
<rect x="200" y="64" width="186" height="72" fill="url(#paint2_linear_1481_75415)"/> <rect x="200" y="64" width="186" height="72" fill="url(#paint2_linear_1481_75415)"/>
<rect x="201.5" y="65.5" width="183" height="69" stroke="#000" stroke-opacity=".12" stroke-width="3"/> <rect x="201.5" y="65.5" width="183" height="69" stroke="#000" stroke-opacity=".12" stroke-width="3"/>

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

22
application/src/main/data/json/system/scada_symbols/cross-pipe.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Cross pipe", "title": "Cross pipe",
"description": "Cross pipe with configurable left/right/top/bottom fluid and leak visualizations.", "description": "Cross pipe with configurable left/right/top/bottom fluid and leak visualizations.",
"searchTags": [ "searchTags": [
@ -8,7 +7,7 @@
], ],
"widgetSizeX": 1, "widgetSizeX": 1,
"widgetSizeY": 1, "widgetSizeY": 1,
"stateRenderFunction": "var leftLiquidPattern = prepareLiquidPattern('left-fluid');\nvar rightLiquidPattern = prepareLiquidPattern('right-fluid');\nvar topLiquidPattern = prepareLiquidPattern('top-fluid');\nvar bottomLiquidPattern = prepareLiquidPattern('bottom-fluid');\n\nupdateLiquidPatternAnimation(leftLiquidPattern, 'left');\nupdateLiquidPatternAnimation(rightLiquidPattern, 'right');\nupdateLiquidPatternAnimation(topLiquidPattern, 'top');\nupdateLiquidPatternAnimation(bottomLiquidPattern, 'bottom');\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill');\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, prefix) {\n if (liquidPattern) {\n var fluid = ctx.values[prefix + 'Fluid'] && !ctx.values.leak;\n var flow = ctx.values[prefix + 'Flow'];\n var flowDirection = ctx.values[prefix + 'FlowDirection'];\n var flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(flowAnimationSpeed);\n }\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n}\n\n", "stateRenderFunction": "var leftLiquidPattern = prepareLiquidPattern('left-fluid');\nvar rightLiquidPattern = prepareLiquidPattern('right-fluid');\nvar topLiquidPattern = prepareLiquidPattern('top-fluid');\nvar bottomLiquidPattern = prepareLiquidPattern('bottom-fluid');\n\nupdateLiquidPatternAnimation(leftLiquidPattern, 'left');\nupdateLiquidPatternAnimation(rightLiquidPattern, 'right');\nupdateLiquidPatternAnimation(topLiquidPattern, 'top');\nupdateLiquidPatternAnimation(bottomLiquidPattern, 'bottom');\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill').first();\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, prefix) {\n if (liquidPattern) {\n var fluid = ctx.values[prefix + 'Fluid'] && !ctx.values.leak;\n var flow = ctx.values[prefix + 'Flow'];\n var flowDirection = ctx.values[prefix + 'FlowDirection'];\n var flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n var fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(flowAnimationSpeed);\n }\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}\n",
"tags": [ "tags": [
{ {
"tag": "bottom-fluid", "tag": "bottom-fluid",
@ -741,7 +740,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><g clip-path="url(#clip0_1245_66442)"> }]]></tb:metadata>
<g clip-path="url(#clip0_1245_66442)">
<path d="m64 186v-172h72v172h-72z" fill="#fff" tb:tag="pipe-background"/> <path d="m64 186v-172h72v172h-72z" fill="#fff" tb:tag="pipe-background"/>
<path d="m64 186v-172h72v172h-72z" fill="url(#paint0_linear_1245_66442)"/> <path d="m64 186v-172h72v172h-72z" fill="url(#paint0_linear_1245_66442)"/>
<path d="m65.5 184.5v-169h69v169h-69z" stroke="#000" stroke-opacity=".12" stroke-width="3"/> <path d="m65.5 184.5v-169h69v169h-69z" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
@ -793,7 +793,7 @@
<clipPath id="clip0_1245_66442"> <clipPath id="clip0_1245_66442">
<rect width="200" height="200" fill="#fff"/> <rect width="200" height="200" fill="#fff"/>
</clipPath> </clipPath>
<pattern id="liquid" width="172" height="72" patternUnits="userSpaceOnUse"> <pattern id="baseLiquid" width="172" height="72" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/> <circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/> <circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/> <circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
@ -1013,10 +1013,14 @@
<stop stop-color="#727171" stop-opacity=".35" offset=".71855"/> <stop stop-color="#727171" stop-opacity=".35" offset=".71855"/>
<stop stop-color="#727171" offset="1"/> <stop stop-color="#727171" offset="1"/>
</linearGradient> </linearGradient>
<pattern id="left-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/> <pattern id="base-left-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#baseLiquid"/>
<pattern id="right-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/> <pattern id="left-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#base-left-liquid)"/></pattern>
<pattern id="top-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/> <pattern id="base-right-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#baseLiquid"/>
<pattern id="bottom-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/> <pattern id="right-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#base-right-liquid)"/></pattern>
<pattern id="base-top-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#baseLiquid"/>
<pattern id="top-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#base-top-liquid)"/></pattern>
<pattern id="base-bottom-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#baseLiquid"/>
<pattern id="bottom-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#base-bottom-liquid)"/></pattern>
</defs><rect x="14" y="64" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="left-fluid-background"/><rect transform="rotate(90)" x="14" y="-136" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="top-fluid-background"/><rect transform="rotate(90)" x="136" y="-136" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="bottom-fluid-background"/><rect x="136" y="64" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="right-fluid-background"/><rect x="14" y="64" width="50" height="72" fill="url(#left-liquid)" stroke-width="0" style="display: none;" tb:tag="left-fluid"/><rect x="136" y="64" width="50" height="72" fill="url(#right-liquid)" stroke-width="0" style="display: none;" tb:tag="right-fluid"/><rect transform="rotate(90)" x="14" y="-136" width="50" height="72" fill="url(#top-liquid)" stroke-width="0" style="display: none;" tb:tag="top-fluid"/><rect transform="rotate(90)" x="136" y="-136" width="50" height="72" fill="url(#bottom-liquid)" stroke-width="0" style="display: none;" tb:tag="bottom-fluid"/><g transform="translate(64 64)" style="display: none;" tb:tag="overlay"> </defs><rect x="14" y="64" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="left-fluid-background"/><rect transform="rotate(90)" x="14" y="-136" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="top-fluid-background"/><rect transform="rotate(90)" x="136" y="-136" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="bottom-fluid-background"/><rect x="136" y="64" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="right-fluid-background"/><rect x="14" y="64" width="50" height="72" fill="url(#left-liquid)" stroke-width="0" style="display: none;" tb:tag="left-fluid"/><rect x="136" y="64" width="50" height="72" fill="url(#right-liquid)" stroke-width="0" style="display: none;" tb:tag="right-fluid"/><rect transform="rotate(90)" x="14" y="-136" width="50" height="72" fill="url(#top-liquid)" stroke-width="0" style="display: none;" tb:tag="top-fluid"/><rect transform="rotate(90)" x="136" y="-136" width="50" height="72" fill="url(#bottom-liquid)" stroke-width="0" style="display: none;" tb:tag="bottom-fluid"/><g transform="translate(64 64)" style="display: none;" tb:tag="overlay">
<path d="m0 72 36-36-36-36z" fill="#fff" style=""/> <path d="m0 72 36-36-36-36z" fill="#fff" style=""/>
<path d="m0 72 36-36-36-36z" fill="url(#paint0_linear_1281_37969)" style="fill: url(&quot;#paint0_linear_1281_37969&quot;);"/> <path d="m0 72 36-36-36-36z" fill="url(#paint0_linear_1281_37969)" style="fill: url(&quot;#paint0_linear_1281_37969&quot;);"/>

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 60 KiB

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

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="600" height="1e3" fill="none" version="1.1" viewBox="0 0 600 1e3"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="600" height="1e3" fill="none" version="1.1" viewBox="0 0 600 1e3"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Cylindrical tank", "title": "Cylindrical tank",
"description": "Cylindrical tank with current volume value and level visualizations.", "description": "Cylindrical tank with current volume value and level visualizations.",
"searchTags": [ "searchTags": [
@ -24,17 +23,17 @@
}, },
{ {
"tag": "fluid", "tag": "fluid",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*900; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*900; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n",
"actions": null "actions": null
}, },
{ {
"tag": "fluid-background", "tag": "fluid-background",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*900; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*900; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n",
"actions": null "actions": null
}, },
{ {
"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', 'dominant-baseline': 'middle', class: 'majorTickText'});\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.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(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 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}",
"actions": null "actions": null
}, },
{ {
@ -560,7 +559,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><path d="m0 27c0-6.6274 5.3726-12 12-12h576c6.627 0 12 5.3726 12 12v948.02c0 3.768-1.746 7.322-5.28 8.627-64.25 23.737-536.96 20.807-590.57-0.264-2.9213-1.148-4.1543-4.137-4.1543-7.276v-949.1z" fill="#E5E5E5" tb:tag="background"/><path d="m0 27c0-6.6274 5.3726-12 12-12h576c6.627 0 12 5.3726 12 12v948.02c0 3.768-1.746 7.322-5.28 8.627-64.25 23.737-536.96 20.807-590.57-0.264-2.9213-1.148-4.1543-4.137-4.1543-7.276v-949.1z" fill="url(#paint0_linear_1690_149726)"/><path d="m0 26.996c0-6.6274 5.3726-12 12-12h576c6.627 0 12 5.3726 12 12v948.08c0 3.487-1.49 6.781-4.751 8.015-58.505 22.139-525.21 22.609-589.71 1.011-3.7027-1.24-5.5357-4.859-5.5357-8.764v-948.34z" fill="#4A4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="8" y="-968" width="584" height="201" fill="#1ec1f4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="8" y="-968" width="584" height="201" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-150-inside-1_1690_149726" fill="white"> }]]></tb:metadata>
<path d="m0 27c0-6.6274 5.3726-12 12-12h576c6.627 0 12 5.3726 12 12v948.02c0 3.768-1.746 7.322-5.28 8.627-64.25 23.737-536.96 20.807-590.57-0.264-2.9213-1.148-4.1543-4.137-4.1543-7.276v-949.1z" fill="#E5E5E5" tb:tag="background"/><path d="m0 27c0-6.6274 5.3726-12 12-12h576c6.627 0 12 5.3726 12 12v948.02c0 3.768-1.746 7.322-5.28 8.627-64.25 23.737-536.96 20.807-590.57-0.264-2.9213-1.148-4.1543-4.137-4.1543-7.276v-949.1z" fill="url(#paint0_linear_1690_149726)"/><path d="m0 26.996c0-6.6274 5.3726-12 12-12h576c6.627 0 12 5.3726 12 12v948.08c0 3.487-1.49 6.781-4.751 8.015-58.505 22.139-525.21 22.609-589.71 1.011-3.7027-1.24-5.5357-4.859-5.5357-8.764v-948.34z" fill="#4A4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="8" y="-968" width="584" height="201" fill="#1ec1f4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="8" y="-968" width="584" height="201" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-150-inside-1_1690_149726" fill="white">
<path d="m12 20c-6.6274 0-12 5.3726-12 12v943.85c0 3.15 1.2395 6.145 4.1742 7.29 53.699 20.946 526.2 23.856 590.52 0.263 3.547-1.302 5.301-4.861 5.301-8.64v-942.77c0-6.6274-5.373-12-12-12h-576zm391 46c-3.866 0-7 3.134-7 7v888c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-888c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill-rule="evenodd"/> <path d="m12 20c-6.6274 0-12 5.3726-12 12v943.85c0 3.15 1.2395 6.145 4.1742 7.29 53.699 20.946 526.2 23.856 590.52 0.263 3.547-1.302 5.301-4.861 5.301-8.64v-942.77c0-6.6274-5.373-12-12-12h-576zm391 46c-3.866 0-7 3.134-7 7v888c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-888c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill-rule="evenodd"/>
</mask><g tb:tag="top-layer"> </mask><g tb:tag="top-layer">
<path d="m12 20c-6.6274 0-12 5.3726-12 12v943.85c0 3.15 1.2395 6.145 4.1742 7.29 53.699 20.946 526.2 23.856 590.52 0.263 3.547-1.302 5.301-4.861 5.301-8.64v-942.77c0-6.6274-5.373-12-12-12h-576zm391 46c-3.866 0-7 3.134-7 7v888c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-888c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill="#E5E5E5" fill-rule="evenodd" tb:tag="background"/> <path d="m12 20c-6.6274 0-12 5.3726-12 12v943.85c0 3.15 1.2395 6.145 4.1742 7.29 53.699 20.946 526.2 23.856 590.52 0.263 3.547-1.302 5.301-4.861 5.301-8.64v-942.77c0-6.6274-5.373-12-12-12h-576zm391 46c-3.866 0-7 3.134-7 7v888c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-888c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill="#E5E5E5" fill-rule="evenodd" tb:tag="background"/>
@ -633,7 +633,7 @@
</g><g transform="translate(-1,409)" filter="url(#filter0_ii_1687_130892)" tb:tag="value-box"> </g><g transform="translate(-1,409)" filter="url(#filter0_ii_1687_130892)" tb:tag="value-box">
<path d="m180 62c0-6.6274 5.373-12 12-12h216c6.627 0 12 5.3726 12 12v56c0 6.627-5.373 12-12 12h-216c-6.627 0-12-5.373-12-12z" fill="#f3f3f3" tb:tag="value-box-background"/> <path d="m180 62c0-6.6274 5.373-12 12-12h216c6.627 0 12 5.3726 12 12v56c0 6.627-5.373 12-12 12h-216c-6.627 0-12-5.373-12-12z" fill="#f3f3f3" tb:tag="value-box-background"/>
<path d="m192 51.5h216c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-216c-5.799 0-10.5-4.701-10.5-10.5v-56c0-5.799 4.701-10.5 10.5-10.5z" stroke="#727171" stroke-width="3"/> <path d="m192 51.5h216c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-216c-5.799 0-10.5-4.701-10.5-10.5v-56c0-5.799 4.701-10.5 10.5-10.5z" stroke="#727171" stroke-width="3"/>
<text x="301.5625" y="93.546875" dominant-baseline="middle" fill="#727171" font-family="Roboto" font-size="32px" font-weight="500" text-anchor="middle" tb:tag="value-text" xml:space="preserve"><tspan>1660 gal</tspan></text> <text x="301.5625" y="93.546875" fill="#727171" font-family="Roboto" font-size="32px" font-weight="500" tb:tag="value-text" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">1660 gal</tspan></text>
</g><ellipse cx="300" cy="16" rx="292" ry="16" fill="#5D5C5C"/><path d="m201.79 0s-201.79 0-201.79 167.5v820.9c0 6.628 5.3726 11.601 12 11.601h576c6.627 0 12-4.973 12-11.601v-820.9c0-167.5-198.21-167.5-198.21-167.5h-101.79zm201.21 203c-3.866 0-7 3.134-7 7v751c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-751c0-3.866-3.134-7-7-7z" fill="#000" fill-opacity="0" tb:tag="clickArea"/><defs> </g><ellipse cx="300" cy="16" rx="292" ry="16" fill="#5D5C5C"/><path d="m201.79 0s-201.79 0-201.79 167.5v820.9c0 6.628 5.3726 11.601 12 11.601h576c6.627 0 12-4.973 12-11.601v-820.9c0-167.5-198.21-167.5-198.21-167.5h-101.79zm201.21 203c-3.866 0-7 3.134-7 7v751c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-751c0-3.866-3.134-7-7-7z" fill="#000" fill-opacity="0" tb:tag="clickArea"/><defs>
<linearGradient id="paint0_linear_1690_149726" x1="600" x2=".018833" y1="510.96" y2="504.56" gradientUnits="userSpaceOnUse"> <linearGradient id="paint0_linear_1690_149726" x1="600" x2=".018833" y1="510.96" y2="504.56" gradientUnits="userSpaceOnUse">
<stop stop-color="#020202" stop-opacity=".35" offset="0"/> <stop stop-color="#020202" stop-opacity=".35" offset="0"/>
@ -653,7 +653,7 @@
<stop stop-color="#020202" stop-opacity=".12" offset=".76303"/> <stop stop-color="#020202" stop-opacity=".12" offset=".76303"/>
<stop stop-color="#020202" stop-opacity=".35" offset="1"/> <stop stop-color="#020202" stop-opacity=".35" offset="1"/>
</linearGradient> </linearGradient>
<pattern id="liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid"> <pattern id="base-liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/> <circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/>
<circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/> <circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/>
<circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/> <circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/>
@ -801,6 +801,7 @@
<circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/> <circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/>
<path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/> <path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/>
</pattern> </pattern>
<pattern id="liquid" width="584" height="201" patternUnits="userSpaceOnUse"><rect width="584" height="201" x="0" y="0" stroke-width="0" fill="url(#base-liquid)"/></pattern>
<linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse"> <linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/> <stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".05" offset="1"/> <stop stop-opacity=".05" offset="1"/>

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 112 KiB

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

@ -24,17 +24,17 @@
}, },
{ {
"tag": "fluid", "tag": "fluid",
"stateRenderFunction": "if (!ctx.properties.transparent) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*895; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n", "stateRenderFunction": "if (!ctx.properties.transparent) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*895; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n",
"actions": null "actions": null
}, },
{ {
"tag": "fluid-background", "tag": "fluid-background",
"stateRenderFunction": "if (!ctx.properties.transparent) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*895; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n", "stateRenderFunction": "if (!ctx.properties.transparent) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*895; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n",
"actions": null "actions": null
}, },
{ {
"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', 'dominant-baseline': 'middle', class: 'majorTickText'});\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.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(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 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}",
"actions": null "actions": null
}, },
{ {
@ -626,7 +626,7 @@
</g><path d="m3.0002 1143.6-1.7e-4 -141.73c16.904 3.91 46.87 7.48 86.628 10.71 43.009 3.48 97.728 6.56 160.31 9.17 125.17 5.22 281.88 8.54 439.46 9.36 157.58 0.81 316.06-0.88 444.77-5.69 64.35-2.41 121.29-5.59 166.96-9.64 42.72-3.78 75.8-8.33 95.87-13.77v141.48c0 4.44-2.78 7.94-6.85 8.68-141.99 25.81-431.84 36.08-714.04 34.75-282.22-1.34-556.2-14.27-666.45-34.72-3.9871-0.74-6.6571-4.16-6.6571-8.6z" stroke="#647484" stroke-width="6"/><path d="m1023 1030v145" stroke="#647484" stroke-width="6"/><path d="m701 1030v150" stroke="#647484" stroke-width="6"/><path d="m379 1023v151" stroke="#647484" stroke-width="6"/><path d="m0 1147c125.45 33.35 1246.3 46.29 1400 0v61.71c0 5.74-3.68 10.62-9.33 11.64-284.6 51.35-1160.4 40.74-1381.5 0.03-5.601-1.04-9.1261-5.87-9.1261-11.56l-1.5804e-4 -61.82z" fill="#E5E5E5" tb:tag="background"/><path d="m0 1147c125.45 33.35 1246.3 46.29 1400 0v61.71c0 5.74-3.68 10.62-9.33 11.64-284.6 51.35-1160.4 40.74-1381.5 0.03-5.601-1.04-9.1261-5.87-9.1261-11.56l-1.5804e-4 -61.82z" fill="url(#paint301_linear_2188_188555)"/><path d="m1.5002 1208.8-1.6e-4 -59.89c16.364 4.02 46.959 7.72 88.25 11.04 42.982 3.45 97.679 6.52 160.25 9.11 125.15 5.18 281.84 8.47 439.41 9.28s316.02-0.87 444.71-5.65c64.33-2.38 121.24-5.55 166.88-9.56 44.11-3.88 77.81-8.56 97.5-14.15v59.71c0 5.09-3.23 9.28-8.1 10.16-142.14 25.65-432.11 35.84-714.3 34.52s-556.29-14.16-666.71-34.49c-4.797-0.88-7.8977-5.01-7.8977-10.08z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m700 0 597.89 200.16c4 1.341 3.46 7.138-0.73 7.694-189.06 25.141-364.28 25.141-597.16 25.141-230.8 0-478.09 0-600.31-24.472-3.9498-0.791-4.1701-6.156-0.3503-7.435l600.66-201.09z" fill="#E5E5E5" tb:tag="background"/><path d="m700 0 597.89 200.16c4 1.341 3.46 7.138-0.73 7.694-189.06 25.141-364.28 25.141-597.16 25.141-230.8 0-478.09 0-600.31-24.472-3.9498-0.791-4.1701-6.156-0.3503-7.435l600.66-201.09z" fill="url(#paint302_linear_2188_188555)"/><path d="m99.815 202.52 600.18-200.93 597.41 200.01c2.51 0.84 2.15 4.439-0.44 4.785-188.96 25.125-364.07 25.127-596.97 25.127-115.41 0-234.9 0-340.83-3.058-105.95-3.059-198.21-9.177-259.19-21.385-2.3046-0.462-2.6651-3.706-0.1686-4.541z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><g filter="url(#filter0_ii_2188_188555)" tb:tag="value-box"> </g><path d="m3.0002 1143.6-1.7e-4 -141.73c16.904 3.91 46.87 7.48 86.628 10.71 43.009 3.48 97.728 6.56 160.31 9.17 125.17 5.22 281.88 8.54 439.46 9.36 157.58 0.81 316.06-0.88 444.77-5.69 64.35-2.41 121.29-5.59 166.96-9.64 42.72-3.78 75.8-8.33 95.87-13.77v141.48c0 4.44-2.78 7.94-6.85 8.68-141.99 25.81-431.84 36.08-714.04 34.75-282.22-1.34-556.2-14.27-666.45-34.72-3.9871-0.74-6.6571-4.16-6.6571-8.6z" stroke="#647484" stroke-width="6"/><path d="m1023 1030v145" stroke="#647484" stroke-width="6"/><path d="m701 1030v150" stroke="#647484" stroke-width="6"/><path d="m379 1023v151" stroke="#647484" stroke-width="6"/><path d="m0 1147c125.45 33.35 1246.3 46.29 1400 0v61.71c0 5.74-3.68 10.62-9.33 11.64-284.6 51.35-1160.4 40.74-1381.5 0.03-5.601-1.04-9.1261-5.87-9.1261-11.56l-1.5804e-4 -61.82z" fill="#E5E5E5" tb:tag="background"/><path d="m0 1147c125.45 33.35 1246.3 46.29 1400 0v61.71c0 5.74-3.68 10.62-9.33 11.64-284.6 51.35-1160.4 40.74-1381.5 0.03-5.601-1.04-9.1261-5.87-9.1261-11.56l-1.5804e-4 -61.82z" fill="url(#paint301_linear_2188_188555)"/><path d="m1.5002 1208.8-1.6e-4 -59.89c16.364 4.02 46.959 7.72 88.25 11.04 42.982 3.45 97.679 6.52 160.25 9.11 125.15 5.18 281.84 8.47 439.41 9.28s316.02-0.87 444.71-5.65c64.33-2.38 121.24-5.55 166.88-9.56 44.11-3.88 77.81-8.56 97.5-14.15v59.71c0 5.09-3.23 9.28-8.1 10.16-142.14 25.65-432.11 35.84-714.3 34.52s-556.29-14.16-666.71-34.49c-4.797-0.88-7.8977-5.01-7.8977-10.08z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m700 0 597.89 200.16c4 1.341 3.46 7.138-0.73 7.694-189.06 25.141-364.28 25.141-597.16 25.141-230.8 0-478.09 0-600.31-24.472-3.9498-0.791-4.1701-6.156-0.3503-7.435l600.66-201.09z" fill="#E5E5E5" tb:tag="background"/><path d="m700 0 597.89 200.16c4 1.341 3.46 7.138-0.73 7.694-189.06 25.141-364.28 25.141-597.16 25.141-230.8 0-478.09 0-600.31-24.472-3.9498-0.791-4.1701-6.156-0.3503-7.435l600.66-201.09z" fill="url(#paint302_linear_2188_188555)"/><path d="m99.815 202.52 600.18-200.93 597.41 200.01c2.51 0.84 2.15 4.439-0.44 4.785-188.96 25.125-364.07 25.127-596.97 25.127-115.41 0-234.9 0-340.83-3.058-105.95-3.059-198.21-9.177-259.19-21.385-2.3046-0.462-2.6651-3.706-0.1686-4.541z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><g filter="url(#filter0_ii_2188_188555)" tb:tag="value-box">
<path d="m580 634c0-6.627 5.373-12 12-12h216c6.627 0 12 5.373 12 12v56c0 6.627-5.373 12-12 12h-216c-6.627 0-12-5.373-12-12v-56z" fill="#fff" tb:tag="value-box-background"/> <path d="m580 634c0-6.627 5.373-12 12-12h216c6.627 0 12 5.373 12 12v56c0 6.627-5.373 12-12 12h-216c-6.627 0-12-5.373-12-12v-56z" fill="#fff" tb:tag="value-box-background"/>
<path d="m581.5 634c0-5.799 4.701-10.5 10.5-10.5h216c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-216c-5.799 0-10.5-4.701-10.5-10.5v-56z" stroke="#727171" stroke-width="3"/> <path d="m581.5 634c0-5.799 4.701-10.5 10.5-10.5h216c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-216c-5.799 0-10.5-4.701-10.5-10.5v-56z" stroke="#727171" stroke-width="3"/>
<text x="705.52533" y="666.35553" dominant-baseline="middle" fill="#727171" font-family="Roboto" font-size="32px" font-weight="500" text-anchor="middle" tb:tag="value-text" xml:space="preserve"><tspan>1660 gal</tspan></text> <text x="705.52533" y="666.35553" fill="#727171" font-family="Roboto" font-size="32px" font-weight="500" tb:tag="value-text" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">1660 gal</tspan></text>
</g><g fill="#D9D9D9" filter="url(#filter1_d_2188_188555)" stroke="#727171" stroke-width="3"> </g><g fill="#D9D9D9" filter="url(#filter1_d_2188_188555)" stroke="#727171" stroke-width="3">
<rect transform="matrix(0 -1 .99255 -.12187 1087.2 414.35)" x="1.4888" y="-1.6828" width="11" height="77" rx="5.5"/> <rect transform="matrix(0 -1 .99255 -.12187 1087.2 414.35)" x="1.4888" y="-1.6828" width="11" height="77" rx="5.5"/>
<rect transform="matrix(0 -1 .99255 -.12187 1087.2 368.35)" x="1.4888" y="-1.6828" width="11" height="77" rx="5.5"/> <rect transform="matrix(0 -1 .99255 -.12187 1087.2 368.35)" x="1.4888" y="-1.6828" width="11" height="77" rx="5.5"/>
@ -900,7 +900,7 @@
<stop stop-color="#727171" stop-opacity=".35" offset=".71855"/> <stop stop-color="#727171" stop-opacity=".35" offset=".71855"/>
<stop stop-color="#727171" offset="1"/> <stop stop-color="#727171" offset="1"/>
</linearGradient> </linearGradient>
<pattern id="liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid"> <pattern id="base-liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/> <circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/>
<circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/> <circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/>
<circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/> <circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/>
@ -1048,6 +1048,7 @@
<circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/> <circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/>
<path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/> <path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/>
</pattern> </pattern>
<pattern id="liquid" width="584" height="201" patternUnits="userSpaceOnUse"><rect width="584" height="201" x="0" y="0" stroke-width="0" fill="url(#base-liquid)"/></pattern>
<linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse"> <linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/> <stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".05" offset="1"/> <stop stop-opacity=".05" offset="1"/>

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 138 KiB

10
application/src/main/data/json/system/scada_symbols/horizontal-ball-valve.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Horizontal ball valve", "title": "Horizontal ball valve",
"description": "Horizontal ball valve with open/close animation and state colors.", "description": "Horizontal ball valve with open/close animation and state colors.",
"searchTags": [ "searchTags": [
@ -11,7 +10,7 @@
"tags": [ "tags": [
{ {
"tag": "background", "tag": "background",
"stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar color = opened ? ctx.properties.openedColor : ctx.properties.closedColor;\nif (!openAnimate) {\n element.attr({fill: color});\n} else {\n ctx.api.animate(element, 500).attr({fill: color});\n element.remember('openAnimate', false);\n}\n", "stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar color = opened ? ctx.properties.openedColor : ctx.properties.closedColor;\nif (!openAnimate) {\n element.attr({fill: color});\n} else {\n ctx.api.cssAnimate(element, 500).attr({fill: color});\n element.remember('openAnimate', false);\n}\n",
"actions": null "actions": null
}, },
{ {
@ -25,7 +24,7 @@
}, },
{ {
"tag": "wheel", "tag": "wheel",
"stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar angle = opened ? ctx.properties.openedRotationAngle : ctx.properties.closedRotationAngle;\nif (!openAnimate) {\n element.transform({rotate: angle, originX: 100});\n} else {\n ctx.api.animate(element, 500).transform({rotate: angle, originX: 100});\n element.remember('openAnimate', false);\n}\n", "stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar angle = opened ? ctx.properties.openedRotationAngle : ctx.properties.closedRotationAngle;\nif (!openAnimate) {\n element.transform({rotate: angle, originX: 100});\n} else {\n ctx.api.cssAnimate(element, 500).transform({rotate: angle, originX: 100});\n element.remember('openAnimate', false);\n}\n",
"actions": null "actions": null
} }
], ],
@ -200,7 +199,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><defs> }]]></tb:metadata>
<defs>
<linearGradient id="paint0_linear_1595_99338" x1="58.72" x2="58.3" y1="46.5" y2="153.51" gradientTransform="translate(-1.1e-6)" gradientUnits="userSpaceOnUse"> <linearGradient id="paint0_linear_1595_99338" x1="58.72" x2="58.3" y1="46.5" y2="153.51" gradientTransform="translate(-1.1e-6)" gradientUnits="userSpaceOnUse">
<stop stop-color="#020202" stop-opacity=".35" offset="0"/> <stop stop-color="#020202" stop-opacity=".35" offset="0"/>
<stop stop-color="#020202" stop-opacity=".12" offset=".18316"/> <stop stop-color="#020202" stop-opacity=".12" offset=".18316"/>

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

15
application/src/main/data/json/system/scada_symbols/horizontal-inline-flow-meter.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="200" fill="none" version="1.1" viewBox="0 0 400 200"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="200" fill="none" version="1.1" viewBox="0 0 400 200"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Horizontal inline flow meter", "title": "Horizontal inline flow meter",
"description": "Horizontal inline flow meter component used to display flow related value and render various states. Includes pipe fluid and leak visualizations.", "description": "Horizontal inline flow meter component used to display flow related value and render various states. Includes pipe fluid and leak visualizations.",
"searchTags": [ "searchTags": [
@ -20,7 +19,7 @@
}, },
{ {
"tag": "border", "tag": "border",
"stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}", "stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (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}",
"actions": { "actions": {
"click": { "click": {
"actionFunction": "ctx.api.callAction(event, 'displayClick');" "actionFunction": "ctx.api.callAction(event, 'displayClick');"
@ -38,7 +37,7 @@
}, },
{ {
"tag": "fluid", "tag": "fluid",
"stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill');\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(ctx.values.flowAnimationSpeed);\n }\n} else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}", "stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill').first();\n\nvar fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(ctx.values.flowAnimationSpeed);\n }\n }\n} else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}",
"actions": null "actions": null
}, },
{ {
@ -733,8 +732,9 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><defs> }]]></tb:metadata>
<pattern id="liquid" width="172" height="72" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse"> <defs>
<pattern id="baseLiquid" width="172" height="72" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/> <circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/> <circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/> <circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
@ -765,6 +765,7 @@
<circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/> <circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/>
<path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/> <path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/>
</pattern> </pattern>
<pattern id="liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#baseLiquid)"/></pattern>
<linearGradient id="paint28_linear_1182_32781-5" x1="19.316" x2="21" y1="8.2632" y2="23" gradientTransform="translate(-36,6)" gradientUnits="userSpaceOnUse"> <linearGradient id="paint28_linear_1182_32781-5" x1="19.316" x2="21" y1="8.2632" y2="23" gradientTransform="translate(-36,6)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/> <stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".15" offset="1"/> <stop stop-opacity=".15" offset="1"/>
@ -905,7 +906,7 @@
<stop stop-color="#727171" stop-opacity=".35" offset=".71855"/> <stop stop-color="#727171" stop-opacity=".35" offset=".71855"/>
<stop stop-color="#727171" offset="1"/> <stop stop-color="#727171" offset="1"/>
</linearGradient> </linearGradient>
</defs><rect x="14" y="64" width="372" height="72" fill="#fff" tb:tag="pipe-background"/><rect x="14" y="64" width="372" height="72" fill="url(#paint0_linear_1595_102361)" style="fill:url(#paint0_linear_1595_102361)"/><rect x="15.5" y="65.5" width="369" height="69" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="387.5" y="51.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><rect x="1.5" y="51.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><rect x="14" y="64" width="372" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect x="14" y="64" width="372" height="72" fill="url(#liquid)" stroke-width="0" style="display: none;" tb:tag="fluid"/><path d="m300 100c0 55.228-44.772 100-100 100s-100-44.772-100-100c0-55.228 44.772-100 100-100s100 44.772 100 100zm-188 0c0 48.603 39.401 88.004 88.004 88.004s88.004-39.401 88.004-88.004c0-48.604-39.401-88.004-88.004-88.004s-88.004 39.401-88.004 88.004z" fill="#4a4848" mask="url(#path-5-inside-1_1595_102361)" stroke="#727171" stroke-width="6" tb:tag="border"/><circle cx="200" cy="100" r="88" fill="#fff" tb:tag="background"/><path d="m135.5 88.357c0-2.1302 1.727-3.8571 3.857-3.8571h121.29c2.13 0 3.857 1.7269 3.857 3.8571v23.286c0 2.13-1.727 3.857-3.857 3.857h-121.29c-2.13 0-3.857-1.727-3.857-3.857z" fill="#4a4848" fill-opacity=".06" tb:tag="border"/><path d="m139.36 83c-2.9408 0-5.3574 2.4165-5.3574 5.3574v23.285c0 2.9408 2.4166 5.3574 5.3574 5.3574h121.29c2.9408 0 5.3574-2.4166 5.3574-5.3574v-23.285c0-2.9409-2.4167-5.3574-5.3574-5.3574zm0 3h121.29c1.3192 0 2.3574 1.0379 2.3574 2.3574v23.285c0 1.3192-1.0382 2.3574-2.3574 2.3574h-121.29c-1.3192 0-2.3574-1.0382-2.3574-2.3574v-23.285c0-1.3195 1.0382-2.3574 2.3574-2.3574z" fill="#4a4848" tb:tag="border"/><circle cx="173.5" cy="59.5" r="12.5" fill="url(#paint1_linear_1595_102361)" style="fill:url(#paint1_linear_1595_102361)"/><circle cx="173.5" cy="59.5" r="12" stroke="#000" stroke-opacity=".12"/><circle cx="226.5" cy="59.5" r="12.5" fill="url(#paint2_linear_1595_102361)" style="fill:url(#paint2_linear_1595_102361)"/><circle cx="226.5" cy="59.5" r="12" stroke="#000" stroke-opacity=".12"/><text x="200.00586" y="102.19141" dominant-baseline="middle" fill="#000000" fill-opacity=".87" text-anchor="middle" style="font-family:Roboto;font-size:24px;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-weight:500" tb:tag="value" xml:space="preserve"><tspan>0</tspan></text><text x="199.61719" y="138.77344" dominant-baseline="middle" fill="#000000" fill-opacity=".87" text-anchor="middle" style="font-family:Roboto;font-size:16px;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-weight:500" tb:tag="valueUnits" xml:space="preserve"><tspan>m³/hr</tspan></text><g style="display: none;" tb:tag="broken"> </defs><rect x="14" y="64" width="372" height="72" fill="#fff" tb:tag="pipe-background"/><rect x="14" y="64" width="372" height="72" fill="url(#paint0_linear_1595_102361)" style="fill:url(#paint0_linear_1595_102361)"/><rect x="15.5" y="65.5" width="369" height="69" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="387.5" y="51.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><rect x="1.5" y="51.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><rect x="14" y="64" width="372" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect x="14" y="64" width="372" height="72" fill="url(#liquid)" stroke-width="0" style="display: none;" tb:tag="fluid"/><path d="m300 100c0 55.228-44.772 100-100 100s-100-44.772-100-100c0-55.228 44.772-100 100-100s100 44.772 100 100zm-188 0c0 48.603 39.401 88.004 88.004 88.004s88.004-39.401 88.004-88.004c0-48.604-39.401-88.004-88.004-88.004s-88.004 39.401-88.004 88.004z" fill="#4a4848" mask="url(#path-5-inside-1_1595_102361)" stroke="#727171" stroke-width="6" tb:tag="border"/><circle cx="200" cy="100" r="88" fill="#fff" tb:tag="background"/><path d="m135.5 88.357c0-2.1302 1.727-3.8571 3.857-3.8571h121.29c2.13 0 3.857 1.7269 3.857 3.8571v23.286c0 2.13-1.727 3.857-3.857 3.857h-121.29c-2.13 0-3.857-1.727-3.857-3.857z" fill="#4a4848" fill-opacity=".06" tb:tag="border"/><path d="m139.36 83c-2.9408 0-5.3574 2.4165-5.3574 5.3574v23.285c0 2.9408 2.4166 5.3574 5.3574 5.3574h121.29c2.9408 0 5.3574-2.4166 5.3574-5.3574v-23.285c0-2.9409-2.4167-5.3574-5.3574-5.3574zm0 3h121.29c1.3192 0 2.3574 1.0379 2.3574 2.3574v23.285c0 1.3192-1.0382 2.3574-2.3574 2.3574h-121.29c-1.3192 0-2.3574-1.0382-2.3574-2.3574v-23.285c0-1.3195 1.0382-2.3574 2.3574-2.3574z" fill="#4a4848" tb:tag="border"/><circle cx="173.5" cy="59.5" r="12.5" fill="url(#paint1_linear_1595_102361)" style="fill:url(#paint1_linear_1595_102361)"/><circle cx="173.5" cy="59.5" r="12" stroke="#000" stroke-opacity=".12"/><circle cx="226.5" cy="59.5" r="12.5" fill="url(#paint2_linear_1595_102361)" style="fill:url(#paint2_linear_1595_102361)"/><circle cx="226.5" cy="59.5" r="12" stroke="#000" stroke-opacity=".12"/><text x="200.00586" y="102.19141" fill="#000000" fill-opacity=".87" style="font-family:Roboto;font-size:24px;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-weight:500" tb:tag="value" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">0</tspan></text><text x="199.61719" y="138.77344" fill="#000000" fill-opacity=".87" style="font-family:Roboto;font-size:16px;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-weight:500" tb:tag="valueUnits" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">m³/hr</tspan></text><g style="display: none;" tb:tag="broken">
<circle cx="200" cy="100" r="88" fill="#000" fill-opacity=".24" style=""/> <circle cx="200" cy="100" r="88" fill="#000" fill-opacity=".24" style=""/>
<path d="m253.92 84.931c-0.174 0.5228-0.309 1.1931-0.577 1.6622-0.577 1.6623-1.583 3.2037-2.991 4.1417-0.697 0.3484-1.488 0.6431-2.226 0.8439-1.971 0.6161-3.848 1.2858-5.725 1.9555-1.636 0.6832-3.513 1.3529-5.122 0.682 0.013-0.2414-0.027-0.3888-0.013-0.6302-2.977 0.4146-0.922 2.811-8.792 8.1516-11.067 2.815-27.05 6.112-29.128 6.044-0.724-0.041-1.408 0.066-2.038 0.08-1.113-0.014-2.172-0.122-3.218-0.471-0.764-0.188-1.515-0.617-2.225-0.899-2.038-0.792-4.183-1.396-6.448-1.571 0.603 0.47 1.206 0.939 1.809 1.409 0 0 0.121 0.442 0.081 0.295 0.201-0.134 0.482 0.027 0.616 0.228-0.751 0.442-1.502 0.884-2.199 1.232-3.647 2.024-7.389 3.993-11.304 5.614 3.875-0.897 7.683-2.33 11.411-4.058 0.845-0.389 1.595-0.831 2.574-1.018 2.306-0.549 4.666 0.551 6.824 1.785l0.094 0.054c0.187 0.107 0.469 0.268 0.455 0.509 0.081 0.295-0.228 0.617-0.483 0.845-1.368 1.085-2.642 2.225-3.916 3.364-1.582 1.461-2.883 3.082-4.56 4.489-1.475 1.274-3.084 2.346-4.64 3.324-0.75 0.442-1.408 0.938-2.239 1.085-0.885 0.241-1.756 0.241-2.668 0.093-1.153-0.161-2.212-0.269-3.311-0.524-6.314-1.37-17.674 5.248-23.787 2.873 2.44 1.395 5.027 2.75 7.466 4.145-3.66 0.522-7.133 2.022-10.7 3.469-0.992 0.428-1.984 0.857-3.111 1.085-0.884 0.241-1.903 0.281-2.775 0.28-1.743 0-3.526-0.148-4.973-1.101 0.831 0.724 1.769 1.261 2.667 1.65-0.255 0.228-0.563 0.55-0.818 0.778-2.146 2.01-4.198 4.074-6.29 5.99 1.14-0.469 2.199-1.232 3.165-2.05 1.274-1.139 2.226-2.587 3.768-3.324 1.743-0.87 3.808-0.561 5.806-0.789 3.164-0.307 6.154-1.834 9.131-3.12s5.228-3.036 10.264-4.758c3.63-1.241 9.543-3.131 12.411-2.486 0.912 0.148 1.864 0.443 2.762 0.832 0.617 0.228 1.18 0.55 1.836 0.926 0.751 0.429 1.408 0.805 2.212 1.14 0.469 0.268 0.992 0.443 1.475 0.47 0.576 0.08 1.22-0.174 1.716-0.388 1.783-0.724 3.567-1.447 5.35-2.17-0.295 0.08-0.737 0.2-1.032 0.281-2.36 0.642-4.666 1.191-6.972 0.869-0.483-0.027-1.006-0.202-1.327-0.51-0.094-0.054-0.188-0.107-0.134-0.201-0.362-0.456-0.214-1.368 0.322-1.435 0.282-0.71 1.046-1.394 1.556-1.849 4.157-3.351 8.408-6.647 12.606-9.85 0.456-0.362 0.912-0.724 1.555-0.978 0.644-0.255 1.18-0.322 1.81-0.335 4.572-0.374 19.939-3.129 23.095-4.335 5.049-1.929 5.348-0.685 7.45-3.208 0.335 0.067 7.054-8.7036 7.39-8.6365 2.252 0.4164 4.343 1.9858 6.099 3.4875 1.45 1.565-2.243 6.524-2.272 7.287-0.032 0.867-18.866 17.201-20.503 18.31-0.751 0.442-2.24 0.274-2.533 0.546-0.103 0.096-0.557 1.991 0.264 0.492-0.764-0.029-8.29 2.174-13.599 1.877-0.482-0.027-1.059-0.108-1.582-0.282-0.335-0.067-0.616-0.228-0.844-0.483-0.094-0.054-0.134-0.201-0.174-0.349-0.04-0.147 0.107-0.188 0.161-0.281 2.803-3.378 6.276-6.621 7.203-11.072-0.201 0.134-0.255 0.228-0.456 0.362-0.054 0.094-0.201 0.134-0.308 0.321-1.744 1.743-3.085 4.089-5.003 5.482-0.858 0.63-1.81 1.206-2.441 2.091-0.791 1.166-1.033 2.895-2.052 3.807-0.51 0.455-1.207 0.804-1.797 0.964-1.623 0.442-3.419 0.535-5.001 1.125-0.792 0.295-1.596 0.831-2.387 1.125-0.148 0.04-0.201 0.134-0.349 0.174-1.783 0.724-7.731 4.384-9.661 4.276 1.689 0.095 7.288-3.392 8.963-3.056-1.797 1.836-3.54 3.579-5.525 5.307 1.904-1.152 3.674-2.505 5.311-4.06 0.818-0.777 1.636-1.555 2.588-2.131 1.85-1.058 4.451-0.816 5.51 1.034 0.134 0.202 0.268 0.403 0.254 0.644 0.415 1.234 0.241 2.628 0.12 3.928-0.551 3.794-1.236 7.387-2.029 11.168-0.591 2.775-1.275 5.496-2.764 7.882-0.108 0.188-0.309 0.322-0.496 0.214 0.442 0.751 0.106 1.555-0.417 2.252-0.429 0.751-1.046 1.394-1.569 2.091-0.47 0.604-0.939 1.207-1.409 1.81-2.239 2.828-4.573 5.602-7.135 8.122 1.556-0.978 3.031-2.251 4.184-3.833 0.886-1.112 1.583-2.332 2.509-3.297 0.979-1.059 2.226-1.715 3.205-2.774 0.416-0.51 0.885-1.113 1.395-1.568 0.066 1.407 0.039 2.761 0.106 4.169 0.201-1.005 0.403-2.011 0.604-3.016 0.215-1.247 0.524-2.44 0.832-3.633 0.94-2.949 2.885-5.697 3.395-8.767 0.014-0.241 0.027-0.483 0.041-0.724-0.201 0.134-0.309 0.322-0.51 0.456-0.04-0.148 0.067-0.336 0.027-0.483 0.055-2.708 0.646-5.483 1.371-8.057 0.563-2.293 1.235-4.773 2.965-6.274 0.509-0.455 1.113-0.857 1.622-1.313 0.51-0.456 0.845-1.26 0.738-1.944 3.178 0.323 6.66 1.265 9.329-0.571 2.065-1.434 4.621-0.818 5.075-2.713-0.792 0.735 1.103 1.189 2.659 0.483s23.915-14.808 24.838-26.835c-0.911-1.8908-2.519-3.4332-4.316-4.2114-0.094-0.0537-0.094-0.0537-0.187-0.1073 2.923-1.1923 5.993-2.4246 8.796-4.0593 0.805-0.536 1.703-1.0184 2.28-1.8092 0.671-1.6086 0.832-3.633 0.94-5.5635z" fill="#5c5a5a" style=""/> <path d="m253.92 84.931c-0.174 0.5228-0.309 1.1931-0.577 1.6622-0.577 1.6623-1.583 3.2037-2.991 4.1417-0.697 0.3484-1.488 0.6431-2.226 0.8439-1.971 0.6161-3.848 1.2858-5.725 1.9555-1.636 0.6832-3.513 1.3529-5.122 0.682 0.013-0.2414-0.027-0.3888-0.013-0.6302-2.977 0.4146-0.922 2.811-8.792 8.1516-11.067 2.815-27.05 6.112-29.128 6.044-0.724-0.041-1.408 0.066-2.038 0.08-1.113-0.014-2.172-0.122-3.218-0.471-0.764-0.188-1.515-0.617-2.225-0.899-2.038-0.792-4.183-1.396-6.448-1.571 0.603 0.47 1.206 0.939 1.809 1.409 0 0 0.121 0.442 0.081 0.295 0.201-0.134 0.482 0.027 0.616 0.228-0.751 0.442-1.502 0.884-2.199 1.232-3.647 2.024-7.389 3.993-11.304 5.614 3.875-0.897 7.683-2.33 11.411-4.058 0.845-0.389 1.595-0.831 2.574-1.018 2.306-0.549 4.666 0.551 6.824 1.785l0.094 0.054c0.187 0.107 0.469 0.268 0.455 0.509 0.081 0.295-0.228 0.617-0.483 0.845-1.368 1.085-2.642 2.225-3.916 3.364-1.582 1.461-2.883 3.082-4.56 4.489-1.475 1.274-3.084 2.346-4.64 3.324-0.75 0.442-1.408 0.938-2.239 1.085-0.885 0.241-1.756 0.241-2.668 0.093-1.153-0.161-2.212-0.269-3.311-0.524-6.314-1.37-17.674 5.248-23.787 2.873 2.44 1.395 5.027 2.75 7.466 4.145-3.66 0.522-7.133 2.022-10.7 3.469-0.992 0.428-1.984 0.857-3.111 1.085-0.884 0.241-1.903 0.281-2.775 0.28-1.743 0-3.526-0.148-4.973-1.101 0.831 0.724 1.769 1.261 2.667 1.65-0.255 0.228-0.563 0.55-0.818 0.778-2.146 2.01-4.198 4.074-6.29 5.99 1.14-0.469 2.199-1.232 3.165-2.05 1.274-1.139 2.226-2.587 3.768-3.324 1.743-0.87 3.808-0.561 5.806-0.789 3.164-0.307 6.154-1.834 9.131-3.12s5.228-3.036 10.264-4.758c3.63-1.241 9.543-3.131 12.411-2.486 0.912 0.148 1.864 0.443 2.762 0.832 0.617 0.228 1.18 0.55 1.836 0.926 0.751 0.429 1.408 0.805 2.212 1.14 0.469 0.268 0.992 0.443 1.475 0.47 0.576 0.08 1.22-0.174 1.716-0.388 1.783-0.724 3.567-1.447 5.35-2.17-0.295 0.08-0.737 0.2-1.032 0.281-2.36 0.642-4.666 1.191-6.972 0.869-0.483-0.027-1.006-0.202-1.327-0.51-0.094-0.054-0.188-0.107-0.134-0.201-0.362-0.456-0.214-1.368 0.322-1.435 0.282-0.71 1.046-1.394 1.556-1.849 4.157-3.351 8.408-6.647 12.606-9.85 0.456-0.362 0.912-0.724 1.555-0.978 0.644-0.255 1.18-0.322 1.81-0.335 4.572-0.374 19.939-3.129 23.095-4.335 5.049-1.929 5.348-0.685 7.45-3.208 0.335 0.067 7.054-8.7036 7.39-8.6365 2.252 0.4164 4.343 1.9858 6.099 3.4875 1.45 1.565-2.243 6.524-2.272 7.287-0.032 0.867-18.866 17.201-20.503 18.31-0.751 0.442-2.24 0.274-2.533 0.546-0.103 0.096-0.557 1.991 0.264 0.492-0.764-0.029-8.29 2.174-13.599 1.877-0.482-0.027-1.059-0.108-1.582-0.282-0.335-0.067-0.616-0.228-0.844-0.483-0.094-0.054-0.134-0.201-0.174-0.349-0.04-0.147 0.107-0.188 0.161-0.281 2.803-3.378 6.276-6.621 7.203-11.072-0.201 0.134-0.255 0.228-0.456 0.362-0.054 0.094-0.201 0.134-0.308 0.321-1.744 1.743-3.085 4.089-5.003 5.482-0.858 0.63-1.81 1.206-2.441 2.091-0.791 1.166-1.033 2.895-2.052 3.807-0.51 0.455-1.207 0.804-1.797 0.964-1.623 0.442-3.419 0.535-5.001 1.125-0.792 0.295-1.596 0.831-2.387 1.125-0.148 0.04-0.201 0.134-0.349 0.174-1.783 0.724-7.731 4.384-9.661 4.276 1.689 0.095 7.288-3.392 8.963-3.056-1.797 1.836-3.54 3.579-5.525 5.307 1.904-1.152 3.674-2.505 5.311-4.06 0.818-0.777 1.636-1.555 2.588-2.131 1.85-1.058 4.451-0.816 5.51 1.034 0.134 0.202 0.268 0.403 0.254 0.644 0.415 1.234 0.241 2.628 0.12 3.928-0.551 3.794-1.236 7.387-2.029 11.168-0.591 2.775-1.275 5.496-2.764 7.882-0.108 0.188-0.309 0.322-0.496 0.214 0.442 0.751 0.106 1.555-0.417 2.252-0.429 0.751-1.046 1.394-1.569 2.091-0.47 0.604-0.939 1.207-1.409 1.81-2.239 2.828-4.573 5.602-7.135 8.122 1.556-0.978 3.031-2.251 4.184-3.833 0.886-1.112 1.583-2.332 2.509-3.297 0.979-1.059 2.226-1.715 3.205-2.774 0.416-0.51 0.885-1.113 1.395-1.568 0.066 1.407 0.039 2.761 0.106 4.169 0.201-1.005 0.403-2.011 0.604-3.016 0.215-1.247 0.524-2.44 0.832-3.633 0.94-2.949 2.885-5.697 3.395-8.767 0.014-0.241 0.027-0.483 0.041-0.724-0.201 0.134-0.309 0.322-0.51 0.456-0.04-0.148 0.067-0.336 0.027-0.483 0.055-2.708 0.646-5.483 1.371-8.057 0.563-2.293 1.235-4.773 2.965-6.274 0.509-0.455 1.113-0.857 1.622-1.313 0.51-0.456 0.845-1.26 0.738-1.944 3.178 0.323 6.66 1.265 9.329-0.571 2.065-1.434 4.621-0.818 5.075-2.713-0.792 0.735 1.103 1.189 2.659 0.483s23.915-14.808 24.838-26.835c-0.911-1.8908-2.519-3.4332-4.316-4.2114-0.094-0.0537-0.094-0.0537-0.187-0.1073 2.923-1.1923 5.993-2.4246 8.796-4.0593 0.805-0.536 1.703-1.0184 2.28-1.8092 0.671-1.6086 0.832-3.633 0.94-5.5635z" fill="#5c5a5a" style=""/>
<path d="m221.07 127.62c-1.005-0.202-1.526-0.498-2.558-0.217-2.065 0.562-2.591 1.325-4.575 2.183-2.226 0.844-3.364 0.839-5.79 0.946-1.261 0.026-2.467-0.041-3.62-0.203 0.187 0.979-1.8 2.31-2.564 2.993-0.764 0.684-1.717 1.26-2.106 2.158-0.442 0.992-0.309 2.065-0.416 3.124-0.162 1.153-0.658 2.238-1.061 3.378-0.711 2.332-0.605 4.759-0.351 7.146 0.054-0.094 0.054-0.094 0.108-0.188 0.054-2.708 0.645-5.483 1.37-8.057 0.564-2.293 1.235-4.773 2.965-6.274 0.51-0.455 1.113-0.857 1.623-1.313 0.509-0.456 2.054-1.666 1.946-2.349 3.178 0.322 5.388 1.065 8.056-0.771 2.066-1.434 2.736-1.244 5.15-1.98 0.402-0.268 1.14-0.469 1.823-0.576z" fill="#8b8b8b" style=""/> <path d="m221.07 127.62c-1.005-0.202-1.526-0.498-2.558-0.217-2.065 0.562-2.591 1.325-4.575 2.183-2.226 0.844-3.364 0.839-5.79 0.946-1.261 0.026-2.467-0.041-3.62-0.203 0.187 0.979-1.8 2.31-2.564 2.993-0.764 0.684-1.717 1.26-2.106 2.158-0.442 0.992-0.309 2.065-0.416 3.124-0.162 1.153-0.658 2.238-1.061 3.378-0.711 2.332-0.605 4.759-0.351 7.146 0.054-0.094 0.054-0.094 0.108-0.188 0.054-2.708 0.645-5.483 1.37-8.057 0.564-2.293 1.235-4.773 2.965-6.274 0.51-0.455 1.113-0.857 1.623-1.313 0.509-0.456 2.054-1.666 1.946-2.349 3.178 0.322 5.388 1.065 8.056-0.771 2.066-1.434 2.736-1.244 5.15-1.98 0.402-0.268 1.14-0.469 1.823-0.576z" fill="#8b8b8b" style=""/>

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

11
application/src/main/data/json/system/scada_symbols/horizontal-pipe.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Horizontal pipe", "title": "Horizontal pipe",
"description": "Horizontal pipe with fluid and leak visualizations.", "description": "Horizontal pipe with fluid and leak visualizations.",
"searchTags": [ "searchTags": [
@ -11,7 +10,7 @@
"tags": [ "tags": [
{ {
"tag": "fluid", "tag": "fluid",
"stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill');\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(ctx.values.flowAnimationSpeed);\n }\n} else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}", "stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill').first();\n\nvar fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(ctx.values.flowAnimationSpeed);\n }\n }\n} else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}",
"actions": null "actions": null
}, },
{ {
@ -240,7 +239,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><path d="m186 136h-172v-72h172z" fill="#fff" tb:tag="pipe-background"/><path d="m186 136h-172v-72h172z" fill="url(#paint0_linear_955_27716)"/><g stroke-width="3"> }]]></tb:metadata>
<path d="m186 136h-172v-72h172z" fill="#fff" tb:tag="pipe-background"/><path d="m186 136h-172v-72h172z" fill="url(#paint0_linear_955_27716)"/><g stroke-width="3">
<path d="m184.5 134.5h-169v-69h169z" stroke="#000" stroke-opacity=".12"/> <path d="m184.5 134.5h-169v-69h169z" stroke="#000" stroke-opacity=".12"/>
<rect transform="scale(-1)" x="-198.5" y="-148.5" width="11.286" height="97" rx="5.6429" fill="#d9d9d9" stroke="#727171"/> <rect transform="scale(-1)" x="-198.5" y="-148.5" width="11.286" height="97" rx="5.6429" fill="#d9d9d9" stroke="#727171"/>
<rect transform="scale(-1)" x="-12.5" y="-148.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171"/> <rect transform="scale(-1)" x="-12.5" y="-148.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171"/>
@ -254,7 +254,7 @@
<stop stop-color="#727171" stop-opacity=".35" offset=".71855"/> <stop stop-color="#727171" stop-opacity=".35" offset=".71855"/>
<stop stop-color="#727171" offset="1"/> <stop stop-color="#727171" offset="1"/>
</linearGradient> </linearGradient>
<pattern id="liquid" width="172" height="72" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse"> <pattern id="baseLiquid" width="172" height="72" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/> <circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/> <circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/> <circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
@ -285,6 +285,7 @@
<circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/> <circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/>
<path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/> <path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/>
</pattern> </pattern>
<pattern id="liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#baseLiquid)"/></pattern>
<linearGradient id="paint28_linear_1182_32781-5" x1="19.316" x2="21" y1="8.2632" y2="23" gradientTransform="translate(-36 6)" gradientUnits="userSpaceOnUse"> <linearGradient id="paint28_linear_1182_32781-5" x1="19.316" x2="21" y1="8.2632" y2="23" gradientTransform="translate(-36 6)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/> <stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".15" offset="1"/> <stop stop-opacity=".15" offset="1"/>

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

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

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="1e3" height="600" fill="none" version="1.1" viewBox="0 0 1e3 600"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="1e3" height="600" fill="none" version="1.1" viewBox="0 0 1e3 600"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Horizontal tank", "title": "Horizontal tank",
"description": "Horizontal tank with current volume value and level visualizations.", "description": "Horizontal tank with current volume value and level visualizations.",
"searchTags": [ "searchTags": [
@ -24,17 +23,17 @@
}, },
{ {
"tag": "fluid", "tag": "fluid",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*568; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*568; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n",
"actions": null "actions": null
}, },
{ {
"tag": "fluid-background", "tag": "fluid-background",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*568; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*568; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n",
"actions": null "actions": null
}, },
{ {
"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', 'dominant-baseline': 'middle', class: 'majorTickText'});\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.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(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 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}",
"actions": null "actions": null
}, },
{ {
@ -555,7 +554,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><path d="m174 600c-174 0-174-151.63-174-239 4.9551e-6 -1.386 9.6938e-6 -2.741 1.4223e-5 -4.065 1.18e-8 -4e-3 -1.4223e-5 -113.93-1.4223e-5 -113.94 0-87.373-8.3346e-7 -243 174-243h653.5c173 0 172.5 156.06 172.5 243v118c0 86.939 0 239-172.5 239h-653.5z" fill="#E5E5E5" tb:tag="background"/><path d="m174 600c-174 0-174-151.63-174-239 4.9551e-6 -1.386 9.6938e-6 -2.741 1.4223e-5 -4.065 1.18e-8 -4e-3 -1.4223e-5 -113.93-1.4223e-5 -113.94 0-87.373-8.3346e-7 -243 174-243h653.5c173 0 172.5 156.06 172.5 243v118c0 86.939 0 239-172.5 239h-653.5z" fill="url(#paint0_linear_1694_158298)"/><path d="m177.27 0.4874c198.56-1.0967 560.65 1.9e-5 640.18 0 79.522-1.9e-5 183.55 15 183.55 220v169c0 192.5-105.53 211.5-184.55 211.5s-516.64-0.5-639.18-0.5c-122.53 0-176.55-68.001-176.55-219 5.26e-4 -151-6.59e-4 -49.502-2.47e-4 -119s-22.006-260.91 176.55-262.01z" fill="#4A4848" fill-opacity=".5"/><mask id="mask0_1694_158298" x="17" y="16" width="968" height="570" style="mask-type:alpha" maskUnits="userSpaceOnUse"> }]]></tb:metadata>
<path d="m174 600c-174 0-174-151.63-174-239 4.9551e-6 -1.386 9.6938e-6 -2.741 1.4223e-5 -4.065 1.18e-8 -4e-3 -1.4223e-5 -113.93-1.4223e-5 -113.94 0-87.373-8.3346e-7 -243 174-243h653.5c173 0 172.5 156.06 172.5 243v118c0 86.939 0 239-172.5 239h-653.5z" fill="#E5E5E5" tb:tag="background"/><path d="m174 600c-174 0-174-151.63-174-239 4.9551e-6 -1.386 9.6938e-6 -2.741 1.4223e-5 -4.065 1.18e-8 -4e-3 -1.4223e-5 -113.93-1.4223e-5 -113.94 0-87.373-8.3346e-7 -243 174-243h653.5c173 0 172.5 156.06 172.5 243v118c0 86.939 0 239-172.5 239h-653.5z" fill="url(#paint0_linear_1694_158298)"/><path d="m177.27 0.4874c198.56-1.0967 560.65 1.9e-5 640.18 0 79.522-1.9e-5 183.55 15 183.55 220v169c0 192.5-105.53 211.5-184.55 211.5s-516.64-0.5-639.18-0.5c-122.53 0-176.55-68.001-176.55-219 5.26e-4 -151-6.59e-4 -49.502-2.47e-4 -119s-22.006-260.91 176.55-262.01z" fill="#4A4848" fill-opacity=".5"/><mask id="mask0_1694_158298" x="17" y="16" width="968" height="570" style="mask-type:alpha" maskUnits="userSpaceOnUse">
<path d="m178.68 16h654.82c117.67 0 151.5 98.586 151.5 184.16v204.18c0 135.62-68.502 181.66-162.5 181.66h-627.98c-129 0-177.5-66.558-177.5-181.66s0.0023-129.32 0-212.19c-3e-4 -9.509-5.4994-176.16 161.66-176.16z" fill="#D9D9D9"/> <path d="m178.68 16h654.82c117.67 0 151.5 98.586 151.5 184.16v204.18c0 135.62-68.502 181.66-162.5 181.66h-627.98c-129 0-177.5-66.558-177.5-181.66s0.0023-129.32 0-212.19c-3e-4 -9.509-5.4994-176.16 161.66-176.16z" fill="#D9D9D9"/>
</mask><g mask="url(#mask0_1694_158298)"> </mask><g mask="url(#mask0_1694_158298)">
<rect transform="scale(1,-1)" x="9" y="-585" width="984" height="200" fill="#1EC1F4" fill-opacity=".5" tb:tag="fluid-background"/> <rect transform="scale(1,-1)" x="9" y="-585" width="984" height="200" fill="#1EC1F4" fill-opacity=".5" tb:tag="fluid-background"/>
@ -637,7 +637,7 @@
</mask><g filter="url(#filter0_ii_1694_158298)" tb:tag="value-box"> </mask><g filter="url(#filter0_ii_1694_158298)" tb:tag="value-box">
<path d="m381 272c0-6.627 5.373-12 12-12h216c6.627 0 12 5.373 12 12v56c0 6.627-5.373 12-12 12h-216c-6.627 0-12-5.373-12-12v-56z" fill="#f3f3f3" tb:tag="value-box-background"/> <path d="m381 272c0-6.627 5.373-12 12-12h216c6.627 0 12 5.373 12 12v56c0 6.627-5.373 12-12 12h-216c-6.627 0-12-5.373-12-12v-56z" fill="#f3f3f3" tb:tag="value-box-background"/>
<path d="m382.5 272c0-5.799 4.701-10.5 10.5-10.5h216c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-216c-5.799 0-10.5-4.701-10.5-10.5v-56z" stroke="#727171" stroke-width="3"/> <path d="m382.5 272c0-5.799 4.701-10.5 10.5-10.5h216c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-216c-5.799 0-10.5-4.701-10.5-10.5v-56z" stroke="#727171" stroke-width="3"/>
<text x="499.84497" y="300.37811" dominant-baseline="middle" fill="#727171" font-family="Roboto" font-size="32px" font-weight="500" text-anchor="middle" tb:tag="value-text" xml:space="preserve"><tspan>1660 gal</tspan></text> <text x="499.84497" y="300.37811" fill="#727171" font-family="Roboto" font-size="32px" font-weight="500" tb:tag="value-text" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">1660 gal</tspan></text>
</g><g transform="matrix(1 0 0 .5 .64 .00025)"> </g><g transform="matrix(1 0 0 .5 .64 .00025)">
<path d="m335.68-5e-4s-336.32 0-336.32 201v985.08c0 7.9536 8.9543 13.921 20 13.921h960c11.045 0 20-5.9676 20-13.921v-985.08c0-201-330.35-201-330.35-201h-169.65zm335.35 243.6c-6.4433 0-11.667 3.7608-11.667 8.4v901.2c0 4.6392 5.2233 8.4 11.667 8.4h73.333c6.4433 0 11.667-3.7608 11.667-8.4v-901.2c0-4.6392-5.2233-8.4-11.667-8.4z" fill="#000" fill-opacity="0" tb:tag="clickArea"/> <path d="m335.68-5e-4s-336.32 0-336.32 201v985.08c0 7.9536 8.9543 13.921 20 13.921h960c11.045 0 20-5.9676 20-13.921v-985.08c0-201-330.35-201-330.35-201h-169.65zm335.35 243.6c-6.4433 0-11.667 3.7608-11.667 8.4v901.2c0 4.6392 5.2233 8.4 11.667 8.4h73.333c6.4433 0 11.667-3.7608 11.667-8.4v-901.2c0-4.6392-5.2233-8.4-11.667-8.4z" fill="#000" fill-opacity="0" tb:tag="clickArea"/>
</g><defs> </g><defs>
@ -675,7 +675,7 @@
<stop stop-color="#020202" stop-opacity=".12" offset=".76303"/> <stop stop-color="#020202" stop-opacity=".12" offset=".76303"/>
<stop stop-color="#020202" stop-opacity=".35" offset="1"/> <stop stop-color="#020202" stop-opacity=".35" offset="1"/>
</linearGradient> </linearGradient>
<pattern id="liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid"> <pattern id="base-liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/> <circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/>
<circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/> <circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/>
<circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/> <circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/>
@ -823,6 +823,7 @@
<circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/> <circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/>
<path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/> <path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/>
</pattern> </pattern>
<pattern id="liquid" width="584" height="201" patternUnits="userSpaceOnUse"><rect width="584" height="201" x="0" y="0" stroke-width="0" fill="url(#base-liquid)"/></pattern>
<linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse"> <linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/> <stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".05" offset="1"/> <stop stop-opacity=".05" offset="1"/>

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 114 KiB

10
application/src/main/data/json/system/scada_symbols/horizontal-wheel-valve.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="200" fill="none" version="1.1" viewBox="0 0 400 200"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="200" fill="none" version="1.1" viewBox="0 0 400 200"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Horizontal wheel valve", "title": "Horizontal wheel valve",
"description": "Horizontal wheel valve with open/close animation and state colors.", "description": "Horizontal wheel valve with open/close animation and state colors.",
"searchTags": [ "searchTags": [
@ -11,7 +10,7 @@
"tags": [ "tags": [
{ {
"tag": "background", "tag": "background",
"stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar color = opened ? ctx.properties.openedColor : ctx.properties.closedColor;\nif (!openAnimate) {\n element.attr({fill: color});\n} else {\n ctx.api.animate(element, 500).attr({fill: color});\n element.remember('openAnimate', false);\n}\n", "stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar color = opened ? ctx.properties.openedColor : ctx.properties.closedColor;\nif (!openAnimate) {\n element.attr({fill: color});\n} else {\n ctx.api.cssAnimate(element, 500).attr({fill: color});\n element.remember('openAnimate', false);\n}\n",
"actions": null "actions": null
}, },
{ {
@ -25,7 +24,7 @@
}, },
{ {
"tag": "wheel", "tag": "wheel",
"stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar angle = opened ? ctx.properties.openedRotationAngle : ctx.properties.closedRotationAngle;\nif (!openAnimate) {\n element.transform({rotate: angle});\n} else {\n ctx.api.animate(element, 500).transform({rotate: angle});\n element.remember('openAnimate', false);\n}\n", "stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar angle = opened ? ctx.properties.openedRotationAngle : ctx.properties.closedRotationAngle;\nif (!openAnimate) {\n element.transform({rotate: angle});\n} else {\n ctx.api.cssAnimate(element, 500).transform({rotate: angle});\n element.remember('openAnimate', false);\n}\n",
"actions": null "actions": null
} }
], ],
@ -200,7 +199,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><path d="m200 29.489c-28.161 0-44.15 0.82311-72 5-28.662 4.2988-72 17-72 17v-13.99c0-4.4182-3.5817-8-8-8h-26c-4.4183 0-8 3.5818-8 8v126c0 4.418 3.5817 8 8 8h26c4.4183 0 8-3.582 8-8v-14.01s43.238 12.711 72 17c27.834 4.151 43.859 5.195 72 5 28.16-0.19499 44.15-1.823 72-6 28.663-4.299 72-16 72-16v14.01c0 4.418 3.582 8 8 8h26c4.418 0 8-3.582 8-8v-126c0-4.4182-3.582-8-8-8h-26c-4.418 0-8 3.5818-8 8v13.99s-43.337-12.701-72-17c-27.85-4.1769-43.839-5-72-5z" fill="#1c943e" tb:tag="background"/><rect x="1.5" y="52.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><rect x="14" y="30" width="42" height="142" rx="8" fill="url(#paint0_linear_1343_53957)" style="fill:url(#paint0_linear_1343_53957)"/><rect x="15.5" y="31.5" width="39" height="139" rx="6.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m56 51.988s43.338-12.701 72-17c27.85-4.1769 43.839-5 72-5s44.15 0.8231 72 5c28.663 4.2988 72 17 72 17v98s-43.337 11.701-72 16c-27.85 4.177-43.84 5.806-72 6-28.141 0.195-44.166-0.849-72-5-28.762-4.289-72-17-72-17z" fill="url(#paint1_linear_1343_53957)" style="fill:url(#paint1_linear_1343_53957)"/><path d="m341.67 52.876c0.314 0.09 0.591 0.1699 0.831 0.239v95.722c-0.233 0.062-0.501 0.133-0.802 0.212-1.238 0.328-3.04 0.801-5.292 1.382-4.505 1.163-10.809 2.758-18.008 4.487-14.409 3.46-32.357 7.448-46.62 9.587-27.798 4.169-43.715 5.789-71.788 5.983-28.068 0.194-44.015-0.844-71.769-4.983-14.3-2.133-32.237-6.366-46.632-10.078-7.1905-1.854-13.484-3.574-17.98-4.83-2.2476-0.629-4.0454-1.141-5.2807-1.496-0.3128-0.09-0.5896-0.17-0.8284-0.239v-95.747c0.2396-0.0691 0.5174-0.149 0.8314-0.239 1.2379-0.3548 3.0393-0.8668 5.2909-1.4947 4.5034-1.2558 10.806-2.9751 18.003-4.8284 14.408-3.7103 32.345-7.9439 46.596-10.081 27.761-4.1635 43.674-4.9834 71.778-4.9834s44.017 0.8199 71.778 4.9834c14.251 2.1374 32.189 6.371 46.596 10.081 7.197 1.8533 13.5 3.5726 18.004 4.8284 2.251 0.6279 4.053 1.1399 5.291 1.4947z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="344" y="30" width="42" height="142" rx="8" fill="url(#paint2_linear_1343_53957)" style="fill:url(#paint2_linear_1343_53957)"/><rect x="345.5" y="31.5" width="39" height="139" rx="6.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="387.5" y="52.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><g tb:tag="wheel"> }]]></tb:metadata>
<path d="m200 29.489c-28.161 0-44.15 0.82311-72 5-28.662 4.2988-72 17-72 17v-13.99c0-4.4182-3.5817-8-8-8h-26c-4.4183 0-8 3.5818-8 8v126c0 4.418 3.5817 8 8 8h26c4.4183 0 8-3.582 8-8v-14.01s43.238 12.711 72 17c27.834 4.151 43.859 5.195 72 5 28.16-0.19499 44.15-1.823 72-6 28.663-4.299 72-16 72-16v14.01c0 4.418 3.582 8 8 8h26c4.418 0 8-3.582 8-8v-126c0-4.4182-3.582-8-8-8h-26c-4.418 0-8 3.5818-8 8v13.99s-43.337-12.701-72-17c-27.85-4.1769-43.839-5-72-5z" fill="#1c943e" tb:tag="background"/><rect x="1.5" y="52.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><rect x="14" y="30" width="42" height="142" rx="8" fill="url(#paint0_linear_1343_53957)" style="fill:url(#paint0_linear_1343_53957)"/><rect x="15.5" y="31.5" width="39" height="139" rx="6.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m56 51.988s43.338-12.701 72-17c27.85-4.1769 43.839-5 72-5s44.15 0.8231 72 5c28.663 4.2988 72 17 72 17v98s-43.337 11.701-72 16c-27.85 4.177-43.84 5.806-72 6-28.141 0.195-44.166-0.849-72-5-28.762-4.289-72-17-72-17z" fill="url(#paint1_linear_1343_53957)" style="fill:url(#paint1_linear_1343_53957)"/><path d="m341.67 52.876c0.314 0.09 0.591 0.1699 0.831 0.239v95.722c-0.233 0.062-0.501 0.133-0.802 0.212-1.238 0.328-3.04 0.801-5.292 1.382-4.505 1.163-10.809 2.758-18.008 4.487-14.409 3.46-32.357 7.448-46.62 9.587-27.798 4.169-43.715 5.789-71.788 5.983-28.068 0.194-44.015-0.844-71.769-4.983-14.3-2.133-32.237-6.366-46.632-10.078-7.1905-1.854-13.484-3.574-17.98-4.83-2.2476-0.629-4.0454-1.141-5.2807-1.496-0.3128-0.09-0.5896-0.17-0.8284-0.239v-95.747c0.2396-0.0691 0.5174-0.149 0.8314-0.239 1.2379-0.3548 3.0393-0.8668 5.2909-1.4947 4.5034-1.2558 10.806-2.9751 18.003-4.8284 14.408-3.7103 32.345-7.9439 46.596-10.081 27.761-4.1635 43.674-4.9834 71.778-4.9834s44.017 0.8199 71.778 4.9834c14.251 2.1374 32.189 6.371 46.596 10.081 7.197 1.8533 13.5 3.5726 18.004 4.8284 2.251 0.6279 4.053 1.1399 5.291 1.4947z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="344" y="30" width="42" height="142" rx="8" fill="url(#paint2_linear_1343_53957)" style="fill:url(#paint2_linear_1343_53957)"/><rect x="345.5" y="31.5" width="39" height="139" rx="6.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="387.5" y="52.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><g tb:tag="wheel">
<path d="m300 100c0 55.229-44.772 100-100 100s-100-44.771-100-100c0-55.228 44.772-100 100-100s100 44.772 100 100zm-175.01 0c0 41.428 33.584 75.012 75.012 75.012s75.012-33.584 75.012-75.012-33.584-75.012-75.012-75.012-75.012 33.584-75.012 75.012z" fill="url(#paint3_radial_1343_53957)" style="fill:url(#paint3_radial_1343_53957)"/> <path d="m300 100c0 55.229-44.772 100-100 100s-100-44.771-100-100c0-55.228 44.772-100 100-100s100 44.772 100 100zm-175.01 0c0 41.428 33.584 75.012 75.012 75.012s75.012-33.584 75.012-75.012-33.584-75.012-75.012-75.012-75.012 33.584-75.012 75.012z" fill="url(#paint3_radial_1343_53957)" style="fill:url(#paint3_radial_1343_53957)"/>
<path d="m194.12 74.998c-1.726 0-3.125-1.3991-3.125-3.125v-46.625c0-3.4517 2.798-6.2499 6.25-6.2499h5.5c3.452 0 6.25 2.7982 6.25 6.2499v46.625c0 1.7259-1.399 3.125-3.125 3.125z" fill="url(#paint4_linear_1343_53957)" style="fill:url(#paint4_linear_1343_53957)"/> <path d="m194.12 74.998c-1.726 0-3.125-1.3991-3.125-3.125v-46.625c0-3.4517 2.798-6.2499 6.25-6.2499h5.5c3.452 0 6.25 2.7982 6.25 6.2499v46.625c0 1.7259-1.399 3.125-3.125 3.125z" fill="url(#paint4_linear_1343_53957)" style="fill:url(#paint4_linear_1343_53957)"/>
<path d="m222.98 88.918c-0.612-1.6137 0.2-3.418 1.814-4.0301l43.595-16.534c3.227-1.2241 6.836 0.3999 8.06 3.6273l2.217 5.8439c1.224 3.2274-0.4 6.8361-3.628 8.0602l-43.595 16.535c-1.613 0.612-3.418-0.2-4.03-1.814l-4.433-11.688z" fill="url(#paint5_linear_1343_53957)" style="fill:url(#paint5_linear_1343_53957)"/> <path d="m222.98 88.918c-0.612-1.6137 0.2-3.418 1.814-4.0301l43.595-16.534c3.227-1.2241 6.836 0.3999 8.06 3.6273l2.217 5.8439c1.224 3.2274-0.4 6.8361-3.628 8.0602l-43.595 16.535c-1.613 0.612-3.418-0.2-4.03-1.814l-4.433-11.688z" fill="url(#paint5_linear_1343_53957)" style="fill:url(#paint5_linear_1343_53957)"/>

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

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

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="1e3" height="1e3" fill="none" version="1.1" viewBox="0 0 1e3 1e3"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="1e3" height="1e3" fill="none" version="1.1" viewBox="0 0 1e3 1e3"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Large cylindrical tank", "title": "Large cylindrical tank",
"description": " Large cylindrical tank with current volume value and level visualizations.", "description": " Large cylindrical tank with current volume value and level visualizations.",
"searchTags": [ "searchTags": [
@ -24,17 +23,17 @@
}, },
{ {
"tag": "fluid", "tag": "fluid",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*910; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*910; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n",
"actions": null "actions": null
}, },
{ {
"tag": "fluid-background", "tag": "fluid-background",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*910; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*910; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n",
"actions": null "actions": null
}, },
{ {
"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', 'dominant-baseline': 'middle', class: 'majorTickText'});\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.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(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 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}",
"actions": null "actions": null
}, },
{ {
@ -560,7 +559,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><path d="m0 27c0-6.6274 5.3726-12 12-12h976c6.627 0 12 5.3726 12 12v945.93c0 4.715-2.633 8.925-7.218 10.024-100.86 24.18-899.82 21.26-986.48-0.051-4.0551-0.997-6.2985-4.773-6.2985-8.949v-946.95z" fill="#E5E5E5" tb:tag="background"/><path d="m0 27c0-6.6274 5.3726-12 12-12h976c6.627 0 12 5.3726 12 12v945.93c0 4.715-2.633 8.925-7.218 10.024-100.86 24.18-899.82 21.26-986.48-0.051-4.0551-0.997-6.2985-4.773-6.2985-8.949v-946.95z" fill="url(#paint0_linear_2005_230043)"/><path d="m0 27c0-6.6274 5.3726-12 12-12h976c6.627 0 12 5.3726 12 12v946.27c0 4.482-2.539 8.532-6.897 9.581-93.436 22.488-884.28 22.928-985.53 0.904-4.7131-1.025-7.5713-5.324-7.5713-10.148v-946.61z" fill="#4A4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="8" y="-972" width="984" height="200" fill="#1EC1F4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="8" y="-972" width="984" height="200" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-248-inside-1_2005_230043" fill="white"> }]]></tb:metadata>
<path d="m0 27c0-6.6274 5.3726-12 12-12h976c6.627 0 12 5.3726 12 12v945.93c0 4.715-2.633 8.925-7.218 10.024-100.86 24.18-899.82 21.26-986.48-0.051-4.0551-0.997-6.2985-4.773-6.2985-8.949v-946.95z" fill="#E5E5E5" tb:tag="background"/><path d="m0 27c0-6.6274 5.3726-12 12-12h976c6.627 0 12 5.3726 12 12v945.93c0 4.715-2.633 8.925-7.218 10.024-100.86 24.18-899.82 21.26-986.48-0.051-4.0551-0.997-6.2985-4.773-6.2985-8.949v-946.95z" fill="url(#paint0_linear_2005_230043)"/><path d="m0 27c0-6.6274 5.3726-12 12-12h976c6.627 0 12 5.3726 12 12v946.27c0 4.482-2.539 8.532-6.897 9.581-93.436 22.488-884.28 22.928-985.53 0.904-4.7131-1.025-7.5713-5.324-7.5713-10.148v-946.61z" fill="#4A4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="8" y="-972" width="984" height="200" fill="#1EC1F4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="8" y="-972" width="984" height="200" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-248-inside-1_2005_230043" fill="white">
<path d="m12 15c-6.6274 0-12 5.3726-12 12v946.95c0 4.176 2.2434 7.952 6.2985 8.949 86.66 21.311 885.62 24.231 986.48 0.051 4.585-1.099 7.218-5.309 7.218-10.024v-945.93c0-6.6274-5.373-12-12-12h-976zm707 42c-3.866 0-7 3.134-7 7v901c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-901c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill-rule="evenodd"/> <path d="m12 15c-6.6274 0-12 5.3726-12 12v946.95c0 4.176 2.2434 7.952 6.2985 8.949 86.66 21.311 885.62 24.231 986.48 0.051 4.585-1.099 7.218-5.309 7.218-10.024v-945.93c0-6.6274-5.373-12-12-12h-976zm707 42c-3.866 0-7 3.134-7 7v901c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-901c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill-rule="evenodd"/>
</mask><g tb:tag="top-layer"> </mask><g tb:tag="top-layer">
<path d="m12 15c-6.6274 0-12 5.3726-12 12v946.95c0 4.176 2.2434 7.952 6.2985 8.949 86.66 21.311 885.62 24.231 986.48 0.051 4.585-1.099 7.218-5.309 7.218-10.024v-945.93c0-6.6274-5.373-12-12-12h-976zm707 42c-3.866 0-7 3.134-7 7v901c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-901c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill="#E5E5E5" fill-rule="evenodd" tb:tag="background"/> <path d="m12 15c-6.6274 0-12 5.3726-12 12v946.95c0 4.176 2.2434 7.952 6.2985 8.949 86.66 21.311 885.62 24.231 986.48 0.051 4.585-1.099 7.218-5.309 7.218-10.024v-945.93c0-6.6274-5.373-12-12-12h-976zm707 42c-3.866 0-7 3.134-7 7v901c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-901c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill="#E5E5E5" fill-rule="evenodd" tb:tag="background"/>
@ -633,7 +633,7 @@
</g><ellipse cx="504" cy="16" rx="496" ry="16" fill="#5D5C5C"/><g filter="url(#filter0_ii_2005_230043)" tb:tag="value-box"> </g><ellipse cx="504" cy="16" rx="496" ry="16" fill="#5D5C5C"/><g filter="url(#filter0_ii_2005_230043)" tb:tag="value-box">
<path d="m380 487c0-6.627 5.373-12 12-12h216c6.627 0 12 5.373 12 12v56c0 6.627-5.373 12-12 12h-216c-6.627 0-12-5.373-12-12v-56z" fill="#f3f3f3" tb:tag="value-box-background"/> <path d="m380 487c0-6.627 5.373-12 12-12h216c6.627 0 12 5.373 12 12v56c0 6.627-5.373 12-12 12h-216c-6.627 0-12-5.373-12-12v-56z" fill="#f3f3f3" tb:tag="value-box-background"/>
<path d="m381.5 487c0-5.799 4.701-10.5 10.5-10.5h216c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-216c-5.799 0-10.5-4.701-10.5-10.5v-56z" stroke="#727171" stroke-width="3"/> <path d="m381.5 487c0-5.799 4.701-10.5 10.5-10.5h216c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-216c-5.799 0-10.5-4.701-10.5-10.5v-56z" stroke="#727171" stroke-width="3"/>
<text x="503.10281" y="518.07812" dominant-baseline="middle" fill="#727171" font-family="Roboto" font-size="32px" font-weight="500" text-anchor="middle" tb:tag="value-text" xml:space="preserve"><tspan>1660 gal</tspan></text> <text x="503.10281" y="518.07812" fill="#727171" font-family="Roboto" font-size="32px" font-weight="500" tb:tag="value-text" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">1660 gal</tspan></text>
</g><path d="m336.32-5e-4s-336.32 0-336.32 167.5v820.9c0 6.628 8.9543 11.601 20 11.601h960c11.045 0 20-4.973 20-11.601v-820.9c0-167.5-330.35-167.5-330.35-167.5h-169.65zm335.35 203c-6.4433 0-11.667 3.134-11.667 7v751c0 3.866 5.2233 7 11.667 7h73.333c6.4433 0 11.667-3.134 11.667-7v-751c0-3.866-5.2233-7-11.667-7z" fill="#000" fill-opacity="0" tb:tag="clickArea"/><defs> </g><path d="m336.32-5e-4s-336.32 0-336.32 167.5v820.9c0 6.628 8.9543 11.601 20 11.601h960c11.045 0 20-4.973 20-11.601v-820.9c0-167.5-330.35-167.5-330.35-167.5h-169.65zm335.35 203c-6.4433 0-11.667 3.134-11.667 7v751c0 3.866 5.2233 7 11.667 7h73.333c6.4433 0 11.667-3.134 11.667-7v-751c0-3.866-5.2233-7-11.667-7z" fill="#000" fill-opacity="0" tb:tag="clickArea"/><defs>
<filter id="filter0_ii_2005_230043" x="376" y="471" width="248" height="88" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"> <filter id="filter0_ii_2005_230043" x="376" y="471" width="248" height="88" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
<feFlood flood-opacity="0" result="BackgroundImageFix"/> <feFlood flood-opacity="0" result="BackgroundImageFix"/>
@ -660,7 +660,7 @@
<stop stop-color="#020202" stop-opacity=".12" offset=".76303"/> <stop stop-color="#020202" stop-opacity=".12" offset=".76303"/>
<stop stop-color="#020202" stop-opacity=".35" offset="1"/> <stop stop-color="#020202" stop-opacity=".35" offset="1"/>
</linearGradient> </linearGradient>
<pattern id="liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid"> <pattern id="base-liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/> <circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/>
<circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/> <circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/>
<circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/> <circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/>
@ -808,6 +808,7 @@
<circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/> <circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/>
<path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/> <path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/>
</pattern> </pattern>
<pattern id="liquid" width="584" height="201" patternUnits="userSpaceOnUse"><rect width="584" height="201" x="0" y="0" stroke-width="0" fill="url(#base-liquid)"/></pattern>
<linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse"> <linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/> <stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".05" offset="1"/> <stop stop-opacity=".05" offset="1"/>

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 112 KiB

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

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="1e3" height="1200" fill="none" version="1.1" viewBox="0 0 1e3 1200"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="1e3" height="1200" fill="none" version="1.1" viewBox="0 0 1e3 1200"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Large stand cylindrical tank", "title": "Large stand cylindrical tank",
"description": "Large stand cylindrical tank with current volume value and level visualizations.", "description": "Large stand cylindrical tank with current volume value and level visualizations.",
"searchTags": [ "searchTags": [
@ -25,17 +24,17 @@
}, },
{ {
"tag": "fluid", "tag": "fluid",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*910; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*910; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n",
"actions": null "actions": null
}, },
{ {
"tag": "fluid-background", "tag": "fluid-background",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*910; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*910; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n",
"actions": null "actions": null
}, },
{ {
"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', 'dominant-baseline': 'middle', class: 'majorTickText'});\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.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(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 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}",
"actions": null "actions": null
}, },
{ {
@ -561,7 +560,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><path d="m0 27c0-6.6274 5.3726-12 12-12h976c6.627 0 12 5.3726 12 12v945.93c0 4.715-2.633 8.925-7.218 10.024-100.86 24.18-899.82 21.26-986.48-0.051-4.0551-0.997-6.2985-4.773-6.2985-8.949v-946.95z" fill="#E5E5E5" tb:tag="background"/><path d="m0 27c0-6.6274 5.3726-12 12-12h976c6.627 0 12 5.3726 12 12v945.93c0 4.715-2.633 8.925-7.218 10.024-100.86 24.18-899.82 21.26-986.48-0.051-4.0551-0.997-6.2985-4.773-6.2985-8.949v-946.95z" fill="url(#paint0_linear_2005_230043)"/><path d="m0 27c0-6.6274 5.3726-12 12-12h976c6.627 0 12 5.3726 12 12v946.27c0 4.482-2.539 8.532-6.897 9.581-93.436 22.488-884.28 22.928-985.53 0.904-4.7131-1.025-7.5713-5.324-7.5713-10.148v-946.61z" fill="#4A4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="8" y="-972" width="984" height="200" fill="#1EC1F4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="8" y="-972" width="984" height="200" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-248-inside-1_2005_230043" fill="white"> }]]></tb:metadata>
<path d="m0 27c0-6.6274 5.3726-12 12-12h976c6.627 0 12 5.3726 12 12v945.93c0 4.715-2.633 8.925-7.218 10.024-100.86 24.18-899.82 21.26-986.48-0.051-4.0551-0.997-6.2985-4.773-6.2985-8.949v-946.95z" fill="#E5E5E5" tb:tag="background"/><path d="m0 27c0-6.6274 5.3726-12 12-12h976c6.627 0 12 5.3726 12 12v945.93c0 4.715-2.633 8.925-7.218 10.024-100.86 24.18-899.82 21.26-986.48-0.051-4.0551-0.997-6.2985-4.773-6.2985-8.949v-946.95z" fill="url(#paint0_linear_2005_230043)"/><path d="m0 27c0-6.6274 5.3726-12 12-12h976c6.627 0 12 5.3726 12 12v946.27c0 4.482-2.539 8.532-6.897 9.581-93.436 22.488-884.28 22.928-985.53 0.904-4.7131-1.025-7.5713-5.324-7.5713-10.148v-946.61z" fill="#4A4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="8" y="-972" width="984" height="200" fill="#1EC1F4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="8" y="-972" width="984" height="200" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-248-inside-1_2005_230043" fill="white">
<path d="m12 15c-6.6274 0-12 5.3726-12 12v946.95c0 4.176 2.2434 7.952 6.2985 8.949 86.66 21.311 885.62 24.231 986.48 0.051 4.585-1.099 7.218-5.309 7.218-10.024v-945.93c0-6.6274-5.373-12-12-12h-976zm707 42c-3.866 0-7 3.134-7 7v901c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-901c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill-rule="evenodd"/> <path d="m12 15c-6.6274 0-12 5.3726-12 12v946.95c0 4.176 2.2434 7.952 6.2985 8.949 86.66 21.311 885.62 24.231 986.48 0.051 4.585-1.099 7.218-5.309 7.218-10.024v-945.93c0-6.6274-5.373-12-12-12h-976zm707 42c-3.866 0-7 3.134-7 7v901c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-901c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill-rule="evenodd"/>
</mask><g tb:tag="top-layer"> </mask><g tb:tag="top-layer">
<path d="m12 15c-6.6274 0-12 5.3726-12 12v946.95c0 4.176 2.2434 7.952 6.2985 8.949 86.66 21.311 885.62 24.231 986.48 0.051 4.585-1.099 7.218-5.309 7.218-10.024v-945.93c0-6.6274-5.373-12-12-12h-976zm707 42c-3.866 0-7 3.134-7 7v901c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-901c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill="#E5E5E5" fill-rule="evenodd" tb:tag="background"/> <path d="m12 15c-6.6274 0-12 5.3726-12 12v946.95c0 4.176 2.2434 7.952 6.2985 8.949 86.66 21.311 885.62 24.231 986.48 0.051 4.585-1.099 7.218-5.309 7.218-10.024v-945.93c0-6.6274-5.373-12-12-12h-976zm707 42c-3.866 0-7 3.134-7 7v901c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-901c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill="#E5E5E5" fill-rule="evenodd" tb:tag="background"/>
@ -634,7 +634,7 @@
</g><ellipse cx="504" cy="16" rx="496" ry="16" fill="#5D5C5C"/><g filter="url(#filter0_ii_2005_230043)" tb:tag="value-box"> </g><ellipse cx="504" cy="16" rx="496" ry="16" fill="#5D5C5C"/><g filter="url(#filter0_ii_2005_230043)" tb:tag="value-box">
<path d="m380 487c0-6.627 5.373-12 12-12h216c6.627 0 12 5.373 12 12v56c0 6.627-5.373 12-12 12h-216c-6.627 0-12-5.373-12-12v-56z" fill="#f3f3f3" tb:tag="value-box-background"/> <path d="m380 487c0-6.627 5.373-12 12-12h216c6.627 0 12 5.373 12 12v56c0 6.627-5.373 12-12 12h-216c-6.627 0-12-5.373-12-12v-56z" fill="#f3f3f3" tb:tag="value-box-background"/>
<path d="m381.5 487c0-5.799 4.701-10.5 10.5-10.5h216c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-216c-5.799 0-10.5-4.701-10.5-10.5v-56z" stroke="#727171" stroke-width="3"/> <path d="m381.5 487c0-5.799 4.701-10.5 10.5-10.5h216c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-216c-5.799 0-10.5-4.701-10.5-10.5v-56z" stroke="#727171" stroke-width="3"/>
<text x="503.10281" y="518.07812" dominant-baseline="middle" fill="#727171" font-family="Roboto" font-size="32px" font-weight="500" text-anchor="middle" tb:tag="value-text" xml:space="preserve"><tspan>1660 gal</tspan></text> <text x="503.10281" y="518.07812" fill="#727171" font-family="Roboto" font-size="32px" font-weight="500" tb:tag="value-text" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">1660 gal</tspan></text>
</g><path d="m336.32-5e-4s-336.32 0-336.32 201v985.08c0 7.9536 8.9543 13.921 20 13.921h960c11.045 0 20-5.9676 20-13.921v-985.08c0-201-330.35-201-330.35-201h-169.65zm335.35 243.6c-6.4433 0-11.667 3.7608-11.667 8.4v901.2c0 4.6392 5.2233 8.4 11.667 8.4h73.333c6.4433 0 11.667-3.7608 11.667-8.4v-901.2c0-4.6392-5.2233-8.4-11.667-8.4z" fill="#000" fill-opacity="0" style="stroke-width:1.0954" tb:tag="clickArea"/><rect x="122" y="1163" width="80" height="24" rx="7" fill="#fff"/><rect x="122" y="1163" width="80" height="24" rx="7" fill="url(#paint245_linear_1702_237993)" style="fill:url(#paint245_linear_1702_237993)"/><rect x="123.5" y="1164.5" width="77" height="21" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="798" y="1163" width="80" height="24" rx="7" fill="#fff"/><rect x="798" y="1163" width="80" height="24" rx="7" fill="url(#paint246_linear_1702_237993)" style="fill:url(#paint246_linear_1702_237993)"/><rect x="799.5" y="1164.5" width="77" height="21" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="rotate(10 168.69 996.46)" x="168.69" y="996.46" width="676" height="8" fill="#727171"/><rect transform="rotate(-10 158.11 1114.7)" x="158.11" y="1114.7" width="687.16" height="8" fill="#727171"/><path d="m146 1163v-169l32 1v168z" fill="#fff"/><path d="m146 1163v-169l32 1v168z" fill="url(#paint247_linear_1702_237993)" style="fill:url(#paint247_linear_1702_237993)"/><path d="m147.5 1161.5v-165.95l29 0.906v165.05z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m822 1163v-167l32-1v168z" fill="#fff"/><path d="m822 1163v-167l32-1v168z" fill="url(#paint248_linear_1702_237993)" style="fill:url(#paint248_linear_1702_237993)"/><path d="m823.5 1161.5v-164.05l29-0.906v164.95z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="rotate(10 135.43 1006.5)" x="135.43" y="1006.5" width="751.02" height="12" fill="#838282"/><rect transform="rotate(-10 130.48 1136)" x="130.48" y="1136" width="748.07" height="12" fill="#838282"/><circle cx="870" cy="885" r="32" fill="url(#paint249_radial_1702_237993)" style="fill:url(#paint249_radial_1702_237993)"/><circle cx="870" cy="885" r="30.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><circle cx="130" cy="885" r="32" fill="url(#paint250_radial_1702_237993)" style="fill:url(#paint250_radial_1702_237993)"/><circle cx="130" cy="885" r="30.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m114 1171v-284c0-8.837 7.163-16 16-16s16 7.163 16 16v284z" fill="#fff"/><path d="m114 1171v-284c0-8.837 7.163-16 16-16s16 7.163 16 16v284z" fill="url(#paint251_linear_1702_237993)" style="fill:url(#paint251_linear_1702_237993)"/><path d="m115.5 1169.5v-282.5c0-8.008 6.492-14.5 14.5-14.5s14.5 6.492 14.5 14.5v282.5z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m854 1171v-284c0-8.837 7.163-16 16-16s16 7.163 16 16v284z" fill="#fff"/><path d="m854 1171v-284c0-8.837 7.163-16 16-16s16 7.163 16 16v284z" fill="url(#paint252_linear_1702_237993)" style="fill:url(#paint252_linear_1702_237993)"/><path d="m855.5 1169.5v-282.5c0-8.008 6.492-14.5 14.5-14.5s14.5 6.492 14.5 14.5v282.5z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="80" y="1171" width="100" height="29" rx="7" fill="#fff"/><rect x="80" y="1171" width="100" height="29" rx="7" fill="url(#paint253_linear_1702_237993)" style="fill:url(#paint253_linear_1702_237993)"/><rect x="81.5" y="1172.5" width="97" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="820" y="1171" width="100" height="29" rx="7" fill="#fff"/><rect x="820" y="1171" width="100" height="29" rx="7" fill="url(#paint254_linear_1702_237993)" style="fill:url(#paint254_linear_1702_237993)"/><rect x="821.5" y="1172.5" width="97" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><defs> </g><path d="m336.32-5e-4s-336.32 0-336.32 201v985.08c0 7.9536 8.9543 13.921 20 13.921h960c11.045 0 20-5.9676 20-13.921v-985.08c0-201-330.35-201-330.35-201h-169.65zm335.35 243.6c-6.4433 0-11.667 3.7608-11.667 8.4v901.2c0 4.6392 5.2233 8.4 11.667 8.4h73.333c6.4433 0 11.667-3.7608 11.667-8.4v-901.2c0-4.6392-5.2233-8.4-11.667-8.4z" fill="#000" fill-opacity="0" style="stroke-width:1.0954" tb:tag="clickArea"/><rect x="122" y="1163" width="80" height="24" rx="7" fill="#fff"/><rect x="122" y="1163" width="80" height="24" rx="7" fill="url(#paint245_linear_1702_237993)" style="fill:url(#paint245_linear_1702_237993)"/><rect x="123.5" y="1164.5" width="77" height="21" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="798" y="1163" width="80" height="24" rx="7" fill="#fff"/><rect x="798" y="1163" width="80" height="24" rx="7" fill="url(#paint246_linear_1702_237993)" style="fill:url(#paint246_linear_1702_237993)"/><rect x="799.5" y="1164.5" width="77" height="21" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="rotate(10 168.69 996.46)" x="168.69" y="996.46" width="676" height="8" fill="#727171"/><rect transform="rotate(-10 158.11 1114.7)" x="158.11" y="1114.7" width="687.16" height="8" fill="#727171"/><path d="m146 1163v-169l32 1v168z" fill="#fff"/><path d="m146 1163v-169l32 1v168z" fill="url(#paint247_linear_1702_237993)" style="fill:url(#paint247_linear_1702_237993)"/><path d="m147.5 1161.5v-165.95l29 0.906v165.05z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m822 1163v-167l32-1v168z" fill="#fff"/><path d="m822 1163v-167l32-1v168z" fill="url(#paint248_linear_1702_237993)" style="fill:url(#paint248_linear_1702_237993)"/><path d="m823.5 1161.5v-164.05l29-0.906v164.95z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="rotate(10 135.43 1006.5)" x="135.43" y="1006.5" width="751.02" height="12" fill="#838282"/><rect transform="rotate(-10 130.48 1136)" x="130.48" y="1136" width="748.07" height="12" fill="#838282"/><circle cx="870" cy="885" r="32" fill="url(#paint249_radial_1702_237993)" style="fill:url(#paint249_radial_1702_237993)"/><circle cx="870" cy="885" r="30.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><circle cx="130" cy="885" r="32" fill="url(#paint250_radial_1702_237993)" style="fill:url(#paint250_radial_1702_237993)"/><circle cx="130" cy="885" r="30.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m114 1171v-284c0-8.837 7.163-16 16-16s16 7.163 16 16v284z" fill="#fff"/><path d="m114 1171v-284c0-8.837 7.163-16 16-16s16 7.163 16 16v284z" fill="url(#paint251_linear_1702_237993)" style="fill:url(#paint251_linear_1702_237993)"/><path d="m115.5 1169.5v-282.5c0-8.008 6.492-14.5 14.5-14.5s14.5 6.492 14.5 14.5v282.5z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m854 1171v-284c0-8.837 7.163-16 16-16s16 7.163 16 16v284z" fill="#fff"/><path d="m854 1171v-284c0-8.837 7.163-16 16-16s16 7.163 16 16v284z" fill="url(#paint252_linear_1702_237993)" style="fill:url(#paint252_linear_1702_237993)"/><path d="m855.5 1169.5v-282.5c0-8.008 6.492-14.5 14.5-14.5s14.5 6.492 14.5 14.5v282.5z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="80" y="1171" width="100" height="29" rx="7" fill="#fff"/><rect x="80" y="1171" width="100" height="29" rx="7" fill="url(#paint253_linear_1702_237993)" style="fill:url(#paint253_linear_1702_237993)"/><rect x="81.5" y="1172.5" width="97" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="820" y="1171" width="100" height="29" rx="7" fill="#fff"/><rect x="820" y="1171" width="100" height="29" rx="7" fill="url(#paint254_linear_1702_237993)" style="fill:url(#paint254_linear_1702_237993)"/><rect x="821.5" y="1172.5" width="97" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><defs>
<filter id="filter0_ii_2005_230043" x="376" y="471" width="248" height="88" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"> <filter id="filter0_ii_2005_230043" x="376" y="471" width="248" height="88" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
<feFlood flood-opacity="0" result="BackgroundImageFix"/> <feFlood flood-opacity="0" result="BackgroundImageFix"/>
@ -661,7 +661,7 @@
<stop stop-color="#020202" stop-opacity=".12" offset=".76303"/> <stop stop-color="#020202" stop-opacity=".12" offset=".76303"/>
<stop stop-color="#020202" stop-opacity=".35" offset="1"/> <stop stop-color="#020202" stop-opacity=".35" offset="1"/>
</linearGradient> </linearGradient>
<pattern id="liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid"> <pattern id="base-liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/> <circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/>
<circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/> <circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/>
<circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/> <circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/>
@ -809,6 +809,7 @@
<circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/> <circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/>
<path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/> <path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/>
</pattern> </pattern>
<pattern id="liquid" width="584" height="201" patternUnits="userSpaceOnUse"><rect width="584" height="201" x="0" y="0" stroke-width="0" fill="url(#base-liquid)"/></pattern>
<linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse"> <linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/> <stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".05" offset="1"/> <stop stop-opacity=".05" offset="1"/>

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 119 KiB

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

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="1e3" height="1200" fill="none" version="1.1" viewBox="0 0 1e3 1200"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="1e3" height="1200" fill="none" version="1.1" viewBox="0 0 1e3 1200"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Large stand vertical tank", "title": "Large stand vertical tank",
"description": "Large stand tank with current volume value and level visualizations.", "description": "Large stand tank with current volume value and level visualizations.",
"searchTags": [ "searchTags": [
@ -25,17 +24,17 @@
}, },
{ {
"tag": "fluid", "tag": "fluid",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*763; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*763; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n",
"actions": null "actions": null
}, },
{ {
"tag": "fluid-background", "tag": "fluid-background",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*763; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*763; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n",
"actions": null "actions": null
}, },
{ {
"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', 'dominant-baseline': 'middle', class: 'majorTickText'});\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.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(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 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}",
"actions": null "actions": null
}, },
{ {
@ -561,7 +560,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><path d="m0 167.5c0-167.5 336.31-167.5 336.31-167.5h333.33s330.36 0 330.36 167.5v820.9c0 6.628-5.373 11.601-12 11.601h-976c-6.6274 0-12-4.973-12-11.601v-820.9z" fill="#E5E5E5" tb:tag="background"/><path d="m0 167.5c0-167.5 336.31-167.5 336.31-167.5h333.33s330.36 0 330.36 167.5v820.9c0 6.628-5.373 11.601-12 11.601h-976c-6.6274 0-12-4.973-12-11.601v-820.9z" fill="url(#paint0_linear_1711_311697)"/><path d="m0 178h1e3v809.18c0 6.623-5.366 11.994-11.99 12l-976 0.814c-6.6313 5e-3 -12.01-5.369-12.01-12v-809.99z" fill="#4A4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="12" y="-966" width="984" height="200" fill="#1EC1F4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="12" y="-966" width="984" height="200" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-248-inside-1_1711_311697" fill="white"> }]]></tb:metadata>
<path d="m0 167.5c0-167.5 336.31-167.5 336.31-167.5h333.33s330.36 0 330.36 167.5v820.9c0 6.628-5.373 11.601-12 11.601h-976c-6.6274 0-12-4.973-12-11.601v-820.9z" fill="#E5E5E5" tb:tag="background"/><path d="m0 167.5c0-167.5 336.31-167.5 336.31-167.5h333.33s330.36 0 330.36 167.5v820.9c0 6.628-5.373 11.601-12 11.601h-976c-6.6274 0-12-4.973-12-11.601v-820.9z" fill="url(#paint0_linear_1711_311697)"/><path d="m0 178h1e3v809.18c0 6.623-5.366 11.994-11.99 12l-976 0.814c-6.6313 5e-3 -12.01-5.369-12.01-12v-809.99z" fill="#4A4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="8" y="-966" width="984" height="200" fill="#1EC1F4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="8" y="-966" width="984" height="200" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-248-inside-1_1711_311697" fill="white">
<path d="m336.31 0s-336.31 0-336.31 167.5v820.9c0 6.628 5.3726 11.601 12 11.601h976c6.627 0 12-4.973 12-11.601v-820.9c0-167.5-330.36-167.5-330.36-167.5h-333.33zm401.69 195c-3.866 0-7 3.134-7 7v757c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-757c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill-rule="evenodd"/> <path d="m336.31 0s-336.31 0-336.31 167.5v820.9c0 6.628 5.3726 11.601 12 11.601h976c6.627 0 12-4.973 12-11.601v-820.9c0-167.5-330.36-167.5-330.36-167.5h-333.33zm401.69 195c-3.866 0-7 3.134-7 7v757c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-757c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill-rule="evenodd"/>
</mask><g tb:tag="top-layer"> </mask><g tb:tag="top-layer">
<path d="m336.31 0s-336.31 0-336.31 167.5v820.9c0 6.628 5.3726 11.601 12 11.601h976c6.627 0 12-4.973 12-11.601v-820.9c0-167.5-330.36-167.5-330.36-167.5h-333.33zm401.69 195c-3.866 0-7 3.134-7 7v757c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-757c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill="#E5E5E5" fill-rule="evenodd" tb:tag="background"/> <path d="m336.31 0s-336.31 0-336.31 167.5v820.9c0 6.628 5.3726 11.601 12 11.601h976c6.627 0 12-4.973 12-11.601v-820.9c0-167.5-330.36-167.5-330.36-167.5h-333.33zm401.69 195c-3.866 0-7 3.134-7 7v757c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-757c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill="#E5E5E5" fill-rule="evenodd" tb:tag="background"/>
@ -634,7 +634,7 @@
</g><g filter="url(#filter0_ii_1711_311697)" tb:tag="value-box"> </g><g filter="url(#filter0_ii_1711_311697)" tb:tag="value-box">
<path d="m380 60c0-6.6274 5.373-12 12-12h216c6.627 0 12 5.3726 12 12v56c0 6.627-5.373 12-12 12h-216c-6.627 0-12-5.373-12-12v-56z" fill="#f3f3f3" tb:tag="value-box-background"/> <path d="m380 60c0-6.6274 5.373-12 12-12h216c6.627 0 12 5.3726 12 12v56c0 6.627-5.373 12-12 12h-216c-6.627 0-12-5.373-12-12v-56z" fill="#f3f3f3" tb:tag="value-box-background"/>
<path d="m381.5 60c0-5.799 4.701-10.5 10.5-10.5h216c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-216c-5.799 0-10.5-4.701-10.5-10.5v-56z" stroke="#727171" stroke-width="3"/> <path d="m381.5 60c0-5.799 4.701-10.5 10.5-10.5h216c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-216c-5.799 0-10.5-4.701-10.5-10.5v-56z" stroke="#727171" stroke-width="3"/>
<text x="505" y="90" dominant-baseline="middle" fill="#727171" font-family="Roboto" font-size="32px" font-weight="500" text-anchor="middle" tb:tag="value-text" xml:space="preserve"><tspan>1660 gal</tspan></text> <text x="505" y="90" fill="#727171" font-family="Roboto" font-size="32px" font-weight="500" tb:tag="value-text" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">1660 gal</tspan></text>
</g><mask id="path-316-inside-2_1711_311697" fill="white"> </g><mask id="path-316-inside-2_1711_311697" fill="white">
<path d="m7 181c-3.866 0-7-3.134-7-7s3.134-7 7-7h986c3.866 0 7 3.134 7 7s-3.134 7-7 7h-986z"/> <path d="m7 181c-3.866 0-7-3.134-7-7s3.134-7 7-7h986c3.866 0 7 3.134 7 7s-3.134 7-7 7h-986z"/>
</mask><path d="m7 181c-3.866 0-7-3.134-7-7s3.134-7 7-7h986c3.866 0 7 3.134 7 7s-3.134 7-7 7h-986z" fill="#D9D9D9"/><path d="m7 170h986v-6h-986v6zm986 8h-986v6h986v-6zm4-4c0 2.209-1.791 4-4 4v6c5.523 0 10-4.477 10-10h-6zm-4-4c2.209 0 4 1.791 4 4h6c0-5.523-4.477-10-10-10v6zm-990 4c0-2.209 1.7909-4 4-4v-6c-5.5228 0-10 4.477-10 10h6zm-6 0c0 5.523 4.4771 10 10 10v-6c-2.2091 0-4-1.791-4-4h-6z" fill="#727171" mask="url(#path-316-inside-2_1711_311697)"/><path d="m335.68-5e-4s-336.32 0-336.32 201v985.08c0 7.9536 8.9543 13.921 20 13.921h960c11.045 0 20-5.9676 20-13.921v-985.08c0-201-330.35-201-330.35-201h-169.65zm335.35 243.6c-6.4433 0-11.667 3.7608-11.667 8.4v901.2c0 4.6392 5.2233 8.4 11.667 8.4h73.333c6.4433 0 11.667-3.7608 11.667-8.4v-901.2c0-4.6392-5.2233-8.4-11.667-8.4z" fill="#000" fill-opacity="0" style="stroke-width:1.0954" tb:tag="clickArea"/><rect x="122" y="1163" width="80" height="24" rx="7" fill="#fff"/><rect x="122" y="1163" width="80" height="24" rx="7" fill="url(#paint245_linear_1711_311696)" style="fill:url(#paint245_linear_1711_311696)"/><rect x="123.5" y="1164.5" width="77" height="21" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="798" y="1163" width="80" height="24" rx="7" fill="#fff"/><rect x="798" y="1163" width="80" height="24" rx="7" fill="url(#paint246_linear_1711_311696)" style="fill:url(#paint246_linear_1711_311696)"/><rect x="799.5" y="1164.5" width="77" height="21" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="rotate(10 168.69 996.46)" x="168.69" y="996.46" width="676" height="8" fill="#727171"/><rect transform="rotate(-10 158.11 1114.7)" x="158.11" y="1114.7" width="687.16" height="8" fill="#727171"/><path d="m146 1163v-169l32 1v168z" fill="#fff"/><path d="m146 1163v-169l32 1v168z" fill="url(#paint247_linear_1711_311696)" style="fill:url(#paint247_linear_1711_311696)"/><path d="m147.5 1161.5v-165.95l29 0.906v165.05z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m822 1163v-167l32-1v168z" fill="#fff"/><path d="m822 1163v-167l32-1v168z" fill="url(#paint248_linear_1711_311696)" style="fill:url(#paint248_linear_1711_311696)"/><path d="m823.5 1161.5v-164.05l29-0.906v164.95z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="rotate(10 135.43 1006.5)" x="135.43" y="1006.5" width="751.02" height="12" fill="#838282"/><rect transform="rotate(-10 130.48 1136)" x="130.48" y="1136" width="748.07" height="12" fill="#838282"/><circle cx="870" cy="885" r="32" fill="url(#paint249_radial_1711_311696)" style="fill:url(#paint249_radial_1711_311696)"/><circle cx="870" cy="885" r="30.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><circle cx="130" cy="885" r="32" fill="url(#paint250_radial_1711_311696)" style="fill:url(#paint250_radial_1711_311696)"/><circle cx="130" cy="885" r="30.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m114 1171v-284c0-8.837 7.163-16 16-16s16 7.163 16 16v284z" fill="#fff"/><path d="m114 1171v-284c0-8.837 7.163-16 16-16s16 7.163 16 16v284z" fill="url(#paint251_linear_1711_311696)" style="fill:url(#paint251_linear_1711_311696)"/><path d="m115.5 1169.5v-282.5c0-8.008 6.492-14.5 14.5-14.5s14.5 6.492 14.5 14.5v282.5z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m854 1171v-284c0-8.837 7.163-16 16-16s16 7.163 16 16v284z" fill="#fff"/><path d="m854 1171v-284c0-8.837 7.163-16 16-16s16 7.163 16 16v284z" fill="url(#paint252_linear_1711_311696)" style="fill:url(#paint252_linear_1711_311696)"/><path d="m855.5 1169.5v-282.5c0-8.008 6.492-14.5 14.5-14.5s14.5 6.492 14.5 14.5v282.5z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="80" y="1171" width="100" height="29" rx="7" fill="#fff"/><rect x="80" y="1171" width="100" height="29" rx="7" fill="url(#paint253_linear_1711_311696)" style="fill:url(#paint253_linear_1711_311696)"/><rect x="81.5" y="1172.5" width="97" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="820" y="1171" width="100" height="29" rx="7" fill="#fff"/><rect x="820" y="1171" width="100" height="29" rx="7" fill="url(#paint254_linear_1711_311696)" style="fill:url(#paint254_linear_1711_311696)"/><rect x="821.5" y="1172.5" width="97" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><defs> </mask><path d="m7 181c-3.866 0-7-3.134-7-7s3.134-7 7-7h986c3.866 0 7 3.134 7 7s-3.134 7-7 7h-986z" fill="#D9D9D9"/><path d="m7 170h986v-6h-986v6zm986 8h-986v6h986v-6zm4-4c0 2.209-1.791 4-4 4v6c5.523 0 10-4.477 10-10h-6zm-4-4c2.209 0 4 1.791 4 4h6c0-5.523-4.477-10-10-10v6zm-990 4c0-2.209 1.7909-4 4-4v-6c-5.5228 0-10 4.477-10 10h6zm-6 0c0 5.523 4.4771 10 10 10v-6c-2.2091 0-4-1.791-4-4h-6z" fill="#727171" mask="url(#path-316-inside-2_1711_311697)"/><path d="m335.68-5e-4s-336.32 0-336.32 201v985.08c0 7.9536 8.9543 13.921 20 13.921h960c11.045 0 20-5.9676 20-13.921v-985.08c0-201-330.35-201-330.35-201h-169.65zm335.35 243.6c-6.4433 0-11.667 3.7608-11.667 8.4v901.2c0 4.6392 5.2233 8.4 11.667 8.4h73.333c6.4433 0 11.667-3.7608 11.667-8.4v-901.2c0-4.6392-5.2233-8.4-11.667-8.4z" fill="#000" fill-opacity="0" style="stroke-width:1.0954" tb:tag="clickArea"/><rect x="122" y="1163" width="80" height="24" rx="7" fill="#fff"/><rect x="122" y="1163" width="80" height="24" rx="7" fill="url(#paint245_linear_1711_311696)" style="fill:url(#paint245_linear_1711_311696)"/><rect x="123.5" y="1164.5" width="77" height="21" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="798" y="1163" width="80" height="24" rx="7" fill="#fff"/><rect x="798" y="1163" width="80" height="24" rx="7" fill="url(#paint246_linear_1711_311696)" style="fill:url(#paint246_linear_1711_311696)"/><rect x="799.5" y="1164.5" width="77" height="21" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="rotate(10 168.69 996.46)" x="168.69" y="996.46" width="676" height="8" fill="#727171"/><rect transform="rotate(-10 158.11 1114.7)" x="158.11" y="1114.7" width="687.16" height="8" fill="#727171"/><path d="m146 1163v-169l32 1v168z" fill="#fff"/><path d="m146 1163v-169l32 1v168z" fill="url(#paint247_linear_1711_311696)" style="fill:url(#paint247_linear_1711_311696)"/><path d="m147.5 1161.5v-165.95l29 0.906v165.05z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m822 1163v-167l32-1v168z" fill="#fff"/><path d="m822 1163v-167l32-1v168z" fill="url(#paint248_linear_1711_311696)" style="fill:url(#paint248_linear_1711_311696)"/><path d="m823.5 1161.5v-164.05l29-0.906v164.95z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="rotate(10 135.43 1006.5)" x="135.43" y="1006.5" width="751.02" height="12" fill="#838282"/><rect transform="rotate(-10 130.48 1136)" x="130.48" y="1136" width="748.07" height="12" fill="#838282"/><circle cx="870" cy="885" r="32" fill="url(#paint249_radial_1711_311696)" style="fill:url(#paint249_radial_1711_311696)"/><circle cx="870" cy="885" r="30.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><circle cx="130" cy="885" r="32" fill="url(#paint250_radial_1711_311696)" style="fill:url(#paint250_radial_1711_311696)"/><circle cx="130" cy="885" r="30.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m114 1171v-284c0-8.837 7.163-16 16-16s16 7.163 16 16v284z" fill="#fff"/><path d="m114 1171v-284c0-8.837 7.163-16 16-16s16 7.163 16 16v284z" fill="url(#paint251_linear_1711_311696)" style="fill:url(#paint251_linear_1711_311696)"/><path d="m115.5 1169.5v-282.5c0-8.008 6.492-14.5 14.5-14.5s14.5 6.492 14.5 14.5v282.5z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m854 1171v-284c0-8.837 7.163-16 16-16s16 7.163 16 16v284z" fill="#fff"/><path d="m854 1171v-284c0-8.837 7.163-16 16-16s16 7.163 16 16v284z" fill="url(#paint252_linear_1711_311696)" style="fill:url(#paint252_linear_1711_311696)"/><path d="m855.5 1169.5v-282.5c0-8.008 6.492-14.5 14.5-14.5s14.5 6.492 14.5 14.5v282.5z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="80" y="1171" width="100" height="29" rx="7" fill="#fff"/><rect x="80" y="1171" width="100" height="29" rx="7" fill="url(#paint253_linear_1711_311696)" style="fill:url(#paint253_linear_1711_311696)"/><rect x="81.5" y="1172.5" width="97" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="820" y="1171" width="100" height="29" rx="7" fill="#fff"/><rect x="820" y="1171" width="100" height="29" rx="7" fill="url(#paint254_linear_1711_311696)" style="fill:url(#paint254_linear_1711_311696)"/><rect x="821.5" y="1172.5" width="97" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><defs>
@ -663,7 +663,7 @@
<stop stop-color="#020202" stop-opacity=".12" offset=".76303"/> <stop stop-color="#020202" stop-opacity=".12" offset=".76303"/>
<stop stop-color="#020202" stop-opacity=".35" offset="1"/> <stop stop-color="#020202" stop-opacity=".35" offset="1"/>
</linearGradient> </linearGradient>
<pattern id="liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid"> <pattern id="base-liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/> <circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/>
<circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/> <circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/>
<circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/> <circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/>
@ -811,6 +811,7 @@
<circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/> <circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/>
<path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/> <path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/>
</pattern> </pattern>
<pattern id="liquid" width="584" height="201" patternUnits="userSpaceOnUse"><rect width="584" height="201" x="0" y="0" stroke-width="0" fill="url(#base-liquid)"/></pattern>
<linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse"> <linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/> <stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".05" offset="1"/> <stop stop-opacity=".05" offset="1"/>

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 120 KiB

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

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1e3" height="1e3" fill="none" version="1.1" viewBox="0 0 1e3 1e3"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1e3" height="1e3" fill="none" version="1.1" viewBox="0 0 1e3 1e3"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Large vertical tank", "title": "Large vertical tank",
"description": "Large vertical tank with current volume value and level visualizations.", "description": "Large vertical tank with current volume value and level visualizations.",
"searchTags": [ "searchTags": [
@ -24,17 +23,17 @@
}, },
{ {
"tag": "fluid", "tag": "fluid",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*763; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*763; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n",
"actions": null "actions": null
}, },
{ {
"tag": "fluid-background", "tag": "fluid-background",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*763; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*763; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n",
"actions": null "actions": null
}, },
{ {
"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', 'dominant-baseline': 'middle', class: 'majorTickText'});\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.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(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 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}",
"actions": null "actions": null
}, },
{ {
@ -560,7 +559,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><path d="m0 167.5c0-167.5 336.31-167.5 336.31-167.5h333.33s330.36 0 330.36 167.5v820.9c0 6.628-5.373 11.601-12 11.601h-976c-6.6274 0-12-4.973-12-11.601v-820.9z" fill="#E5E5E5" tb:tag="background"/><path d="m0 167.5c0-167.5 336.31-167.5 336.31-167.5h333.33s330.36 0 330.36 167.5v820.9c0 6.628-5.373 11.601-12 11.601h-976c-6.6274 0-12-4.973-12-11.601v-820.9z" fill="url(#paint0_linear_1711_311697)"/><path d="m0 178h1e3v809.18c0 6.623-5.366 11.994-11.99 12l-976 0.814c-6.6313 5e-3 -12.01-5.369-12.01-12v-809.99z" fill="#4A4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="12" y="-966" width="984" height="200" fill="#1EC1F4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="12" y="-966" width="984" height="200" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-248-inside-1_1711_311697" fill="white"> }]]></tb:metadata>
<path d="m0 167.5c0-167.5 336.31-167.5 336.31-167.5h333.33s330.36 0 330.36 167.5v820.9c0 6.628-5.373 11.601-12 11.601h-976c-6.6274 0-12-4.973-12-11.601v-820.9z" fill="#E5E5E5" tb:tag="background"/><path d="m0 167.5c0-167.5 336.31-167.5 336.31-167.5h333.33s330.36 0 330.36 167.5v820.9c0 6.628-5.373 11.601-12 11.601h-976c-6.6274 0-12-4.973-12-11.601v-820.9z" fill="url(#paint0_linear_1711_311697)"/><path d="m0 178h1e3v809.18c0 6.623-5.366 11.994-11.99 12l-976 0.814c-6.6313 5e-3 -12.01-5.369-12.01-12v-809.99z" fill="#4A4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="8" y="-966" width="984" height="200" fill="#1EC1F4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="8" y="-966" width="984" height="200" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-248-inside-1_1711_311697" fill="white">
<path d="m336.31 0s-336.31 0-336.31 167.5v820.9c0 6.628 5.3726 11.601 12 11.601h976c6.627 0 12-4.973 12-11.601v-820.9c0-167.5-330.36-167.5-330.36-167.5h-333.33zm401.69 195c-3.866 0-7 3.134-7 7v757c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-757c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill-rule="evenodd"/> <path d="m336.31 0s-336.31 0-336.31 167.5v820.9c0 6.628 5.3726 11.601 12 11.601h976c6.627 0 12-4.973 12-11.601v-820.9c0-167.5-330.36-167.5-330.36-167.5h-333.33zm401.69 195c-3.866 0-7 3.134-7 7v757c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-757c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill-rule="evenodd"/>
</mask><g tb:tag="top-layer"> </mask><g tb:tag="top-layer">
<path d="m336.31 0s-336.31 0-336.31 167.5v820.9c0 6.628 5.3726 11.601 12 11.601h976c6.627 0 12-4.973 12-11.601v-820.9c0-167.5-330.36-167.5-330.36-167.5h-333.33zm401.69 195c-3.866 0-7 3.134-7 7v757c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-757c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill="#E5E5E5" fill-rule="evenodd" tb:tag="background"/> <path d="m336.31 0s-336.31 0-336.31 167.5v820.9c0 6.628 5.3726 11.601 12 11.601h976c6.627 0 12-4.973 12-11.601v-820.9c0-167.5-330.36-167.5-330.36-167.5h-333.33zm401.69 195c-3.866 0-7 3.134-7 7v757c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-757c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill="#E5E5E5" fill-rule="evenodd" tb:tag="background"/>
@ -633,7 +633,7 @@
</g><g filter="url(#filter0_ii_1711_311697)" tb:tag="value-box"> </g><g filter="url(#filter0_ii_1711_311697)" tb:tag="value-box">
<path d="m380 60c0-6.6274 5.373-12 12-12h216c6.627 0 12 5.3726 12 12v56c0 6.627-5.373 12-12 12h-216c-6.627 0-12-5.373-12-12v-56z" fill="#f3f3f3" tb:tag="value-box-background"/> <path d="m380 60c0-6.6274 5.373-12 12-12h216c6.627 0 12 5.3726 12 12v56c0 6.627-5.373 12-12 12h-216c-6.627 0-12-5.373-12-12v-56z" fill="#f3f3f3" tb:tag="value-box-background"/>
<path d="m381.5 60c0-5.799 4.701-10.5 10.5-10.5h216c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-216c-5.799 0-10.5-4.701-10.5-10.5v-56z" stroke="#727171" stroke-width="3"/> <path d="m381.5 60c0-5.799 4.701-10.5 10.5-10.5h216c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-216c-5.799 0-10.5-4.701-10.5-10.5v-56z" stroke="#727171" stroke-width="3"/>
<text x="505" y="90" dominant-baseline="middle" fill="#727171" font-family="Roboto" font-size="32px" font-weight="500" text-anchor="middle" tb:tag="value-text" xml:space="preserve"><tspan>1660 gal</tspan></text> <text x="505" y="90" fill="#727171" font-family="Roboto" font-size="32px" font-weight="500" tb:tag="value-text" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">1660 gal</tspan></text>
</g><mask id="path-316-inside-2_1711_311697" fill="white"> </g><mask id="path-316-inside-2_1711_311697" fill="white">
<path d="m7 181c-3.866 0-7-3.134-7-7s3.134-7 7-7h986c3.866 0 7 3.134 7 7s-3.134 7-7 7h-986z"/> <path d="m7 181c-3.866 0-7-3.134-7-7s3.134-7 7-7h986c3.866 0 7 3.134 7 7s-3.134 7-7 7h-986z"/>
</mask><path d="m7 181c-3.866 0-7-3.134-7-7s3.134-7 7-7h986c3.866 0 7 3.134 7 7s-3.134 7-7 7h-986z" fill="#D9D9D9"/><path d="m7 170h986v-6h-986v6zm986 8h-986v6h986v-6zm4-4c0 2.209-1.791 4-4 4v6c5.523 0 10-4.477 10-10h-6zm-4-4c2.209 0 4 1.791 4 4h6c0-5.523-4.477-10-10-10v6zm-990 4c0-2.209 1.7909-4 4-4v-6c-5.5228 0-10 4.477-10 10h6zm-6 0c0 5.523 4.4771 10 10 10v-6c-2.2091 0-4-1.791-4-4h-6z" fill="#727171" mask="url(#path-316-inside-2_1711_311697)"/><path d="m335.68-5e-4s-336.32 0-336.32 167.5v820.9c0 6.628 8.9543 11.601 20 11.601h960c11.045 0 20-4.973 20-11.601v-820.9c0-167.5-330.35-167.5-330.35-167.5h-169.65zm335.35 203c-6.4433 0-11.667 3.134-11.667 7v751c0 3.866 5.2233 7 11.667 7h73.333c6.4433 0 11.667-3.134 11.667-7v-751c0-3.866-5.2233-7-11.667-7z" fill="#000" fill-opacity="0" tb:tag="clickArea"/><defs> </mask><path d="m7 181c-3.866 0-7-3.134-7-7s3.134-7 7-7h986c3.866 0 7 3.134 7 7s-3.134 7-7 7h-986z" fill="#D9D9D9"/><path d="m7 170h986v-6h-986v6zm986 8h-986v6h986v-6zm4-4c0 2.209-1.791 4-4 4v6c5.523 0 10-4.477 10-10h-6zm-4-4c2.209 0 4 1.791 4 4h6c0-5.523-4.477-10-10-10v6zm-990 4c0-2.209 1.7909-4 4-4v-6c-5.5228 0-10 4.477-10 10h6zm-6 0c0 5.523 4.4771 10 10 10v-6c-2.2091 0-4-1.791-4-4h-6z" fill="#727171" mask="url(#path-316-inside-2_1711_311697)"/><path d="m335.68-5e-4s-336.32 0-336.32 167.5v820.9c0 6.628 8.9543 11.601 20 11.601h960c11.045 0 20-4.973 20-11.601v-820.9c0-167.5-330.35-167.5-330.35-167.5h-169.65zm335.35 203c-6.4433 0-11.667 3.134-11.667 7v751c0 3.866 5.2233 7 11.667 7h73.333c6.4433 0 11.667-3.134 11.667-7v-751c0-3.866-5.2233-7-11.667-7z" fill="#000" fill-opacity="0" tb:tag="clickArea"/><defs>
@ -662,7 +662,7 @@
<stop stop-color="#020202" stop-opacity=".12" offset=".76303"/> <stop stop-color="#020202" stop-opacity=".12" offset=".76303"/>
<stop stop-color="#020202" stop-opacity=".35" offset="1"/> <stop stop-color="#020202" stop-opacity=".35" offset="1"/>
</linearGradient> </linearGradient>
<pattern id="liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid"> <pattern id="base-liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/> <circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/>
<circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/> <circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/>
<circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/> <circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/>
@ -810,6 +810,7 @@
<circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/> <circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/>
<path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/> <path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/>
</pattern> </pattern>
<pattern id="liquid" width="584" height="201" patternUnits="userSpaceOnUse"><rect width="584" height="201" x="0" y="0" stroke-width="0" fill="url(#base-liquid)"/></pattern>
<linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse"> <linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/> <stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".05" offset="1"/> <stop stop-opacity=".05" offset="1"/>

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

8
application/src/main/data/json/system/scada_symbols/leak-sensor.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="100" height="100" fill="none" version="1.1" viewBox="0 0 100 100"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="100" height="100" fill="none" version="1.1" viewBox="0 0 100 100"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Leak sensor", "title": "Leak sensor",
"description": "Leak sensor", "description": "Leak sensor",
"searchTags": [ "searchTags": [
@ -24,7 +23,7 @@
}, },
{ {
"tag": "icon", "tag": "icon",
"stateRenderFunction": "var leak = ctx.values.leak;\nif (leak) {\n element.attr({stroke: ctx.properties.leakColor});\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n} else {\n element.attr({stroke: ctx.properties.defaultColor});\n ctx.api.resetAnimation(element);\n}\n", "stateRenderFunction": "var leak = ctx.values.leak;\nif (leak) {\n element.attr({stroke: ctx.properties.leakColor});\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n} else {\n element.attr({stroke: ctx.properties.defaultColor});\n ctx.api.finishCssAnimation(element);\n}\n",
"actions": null "actions": null
} }
], ],
@ -135,7 +134,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><rect x=".5" y=".5" width="99" height="99" rx="23.5" fill="#fff" tb:tag="background"/><rect x=".5" y=".5" width="99" height="99" rx="23.5" fill="url(#paint0_linear_2089_217648)"/><rect x=".5" y=".5" width="99" height="99" rx="23.5" fill="url(#paint1_linear_2089_217648)"/><rect x=".5" y=".5" width="99" height="99" rx="23.5" stroke="#fff"/><path d="m56.668 52.309c0 2.04-0.7902 3.9966-2.1967 5.4391-0.7985 0.819-1.7586 1.4347-2.8035 1.8135m10-6.7401c0-10.962-11.667-19.487-11.667-19.487s-11.667 8.5256-11.667 19.487c0 3.2301 1.2292 6.3283 3.4171 8.6125 2.1879 2.284 5.1556 3.5659 8.2497 3.5659 3.0942 0 6.0615-1.2826 8.2493-3.5666 2.188-2.2842 3.4172-5.3817 3.4172-8.6118z" stroke="#1C943E" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" tb:tag="icon"/><path d="m33.631 0s-33.631 0-33.631 16.75v82.09c0 0.6628 0.89543 1.1601 2 1.1601h96c1.1045 0 2-0.4973 2-1.1601v-82.09c0-16.75-33.035-16.75-33.035-16.75h-16.965zm33.535 20.3c-0.64434 0-1.1666 0.3134-1.1666 0.7v75.1c0 0.3866 0.52234 0.7 1.1666 0.7h7.3336c0.64434 0 1.1666-0.3134 1.1666-0.7v-75.1c0-0.3866-0.52233-0.7-1.1666-0.7z" fill="#000" fill-opacity="0" style="stroke-width:.059761" tb:tag="clickArea"/><defs> }]]></tb:metadata>
<rect x=".5" y=".5" width="99" height="99" rx="23.5" fill="#fff" tb:tag="background"/><rect x=".5" y=".5" width="99" height="99" rx="23.5" fill="url(#paint0_linear_2089_217648)"/><rect x=".5" y=".5" width="99" height="99" rx="23.5" fill="url(#paint1_linear_2089_217648)"/><rect x=".5" y=".5" width="99" height="99" rx="23.5" stroke="#fff"/><path d="m56.668 52.309c0 2.04-0.7902 3.9966-2.1967 5.4391-0.7985 0.819-1.7586 1.4347-2.8035 1.8135m10-6.7401c0-10.962-11.667-19.487-11.667-19.487s-11.667 8.5256-11.667 19.487c0 3.2301 1.2292 6.3283 3.4171 8.6125 2.1879 2.284 5.1556 3.5659 8.2497 3.5659 3.0942 0 6.0615-1.2826 8.2493-3.5666 2.188-2.2842 3.4172-5.3817 3.4172-8.6118z" stroke="#1C943E" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" tb:tag="icon"/><path d="m33.631 0s-33.631 0-33.631 16.75v82.09c0 0.6628 0.89543 1.1601 2 1.1601h96c1.1045 0 2-0.4973 2-1.1601v-82.09c0-16.75-33.035-16.75-33.035-16.75h-16.965zm33.535 20.3c-0.64434 0-1.1666 0.3134-1.1666 0.7v75.1c0 0.3866 0.52234 0.7 1.1666 0.7h7.3336c0.64434 0 1.1666-0.3134 1.1666-0.7v-75.1c0-0.3866-0.52233-0.7-1.1666-0.7z" fill="#000" fill-opacity="0" style="stroke-width:.059761" tb:tag="clickArea"/><defs>
<linearGradient id="paint0_linear_2089_217648" x1="100" x2="1.5002" y1="50.119" y2="49.881" gradientUnits="userSpaceOnUse"> <linearGradient id="paint0_linear_2089_217648" x1="100" x2="1.5002" y1="50.119" y2="49.881" gradientUnits="userSpaceOnUse">
<stop stop-color="#020202" stop-opacity=".2" offset="0"/> <stop stop-color="#020202" stop-opacity=".2" offset="0"/>
<stop stop-color="#020202" stop-opacity=".12" offset=".05"/> <stop stop-color="#020202" stop-opacity=".12" offset=".05"/>

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

6
application/src/main/data/json/system/scada_symbols/left-analog-water-level-meter.svg

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

58
application/src/main/data/json/system/scada_symbols/left-bottom-elbow-pipe.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Left bottom elbow pipe", "title": "Left bottom elbow pipe",
"description": "Left bottom elbow pipe with fluid and leak visualizations.", "description": "Left bottom elbow pipe with fluid and leak visualizations.",
"searchTags": [ "searchTags": [
@ -8,7 +7,7 @@
], ],
"widgetSizeX": 1, "widgetSizeX": 1,
"widgetSizeY": 1, "widgetSizeY": 1,
"stateRenderFunction": "var centerLiquidPattern = prepareLiquidPattern('center-fluid-background');\nvar horizontalLiquidPattern = prepareLiquidPattern('horizontal-fluid');\nvar verticalLiquidPattern = prepareLiquidPattern('vertical-fluid');\n\nvar fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\nvar flowAnimationSpeed = ctx.values.flowAnimationSpeed;\n \nif (horizontalLiquidPattern) {\n updateLiquidPatternAnimation(horizontalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (verticalLiquidPattern) {\n updateLiquidPatternAnimation(verticalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (centerLiquidPattern) {\n updateLiquidPatternAnimation(centerLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, true);\n}\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill');\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, fluid, flow, flowDirection, flowAnimationSpeed, center) {\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection, center);\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection, center);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(flowAnimationSpeed);\n }\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse, center) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n if (center) {\n var delta = deltaX * 1.17 * Math.cos(45*Math.PI/180);\n liquidPattern.animate(1000).ease('-').relative(-delta, delta).loop();\n } else {\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}\n\n", "stateRenderFunction": "var centerLiquidPattern = prepareLiquidPattern('center-fluid-background');\nvar horizontalLiquidPattern = prepareLiquidPattern('horizontal-fluid');\nvar verticalLiquidPattern = prepareLiquidPattern('vertical-fluid');\n\nvar fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\nvar flowAnimationSpeed = ctx.values.flowAnimationSpeed;\n \nif (horizontalLiquidPattern) {\n updateLiquidPatternAnimation(horizontalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (verticalLiquidPattern) {\n updateLiquidPatternAnimation(verticalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (centerLiquidPattern) {\n updateLiquidPatternAnimation(centerLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, true);\n}\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill').first();\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, fluid, flow, flowDirection, flowAnimationSpeed, center) {\n \n var fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n liquidPattern.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection, center);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection, center);\n }\n if (flow && fluidAnimation) {\n fluidAnimation.speed(flowAnimationSpeed);\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse, center) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n if (center) {\n var duration = 1000 * 1.17;\n return ctx.api.cssAnimate(liquidPattern, duration).relative(deltaX, 0).loop();\n } else {\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n }\n}",
"tags": [ "tags": [
{ {
"tag": "center-fluid", "tag": "center-fluid",
@ -251,7 +250,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><g clip-path="url(#clip0_1245_66459)"> }]]></tb:metadata>
<g clip-path="url(#clip0_1245_66459)">
<rect x="14" y="64" width="50" height="72" fill="#fff" tb:tag="pipe-background"/> <rect x="14" y="64" width="50" height="72" fill="#fff" tb:tag="pipe-background"/>
<rect x="14" y="64" width="50" height="72" fill="url(#paint0_linear_1245_66459)" style="fill:url(#paint0_linear_1245_66459)"/> <rect x="14" y="64" width="50" height="72" fill="url(#paint0_linear_1245_66459)" style="fill:url(#paint0_linear_1245_66459)"/>
<rect x="15.5" y="65.5" width="47" height="69" stroke="#000" stroke-opacity=".12" stroke-width="3"/> <rect x="15.5" y="65.5" width="47" height="69" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
@ -264,7 +264,7 @@
<path d="m64 64s30.518 1.7177 50.4 21.6c19.882 19.882 21.6 50.4 21.6 50.4h-72z" fill="url(#paint2_linear_1245_66459)" style="fill:url(#paint2_linear_1245_66459)"/> <path d="m64 64s30.518 1.7177 50.4 21.6c19.882 19.882 21.6 50.4 21.6 50.4h-72z" fill="url(#paint2_linear_1245_66459)" style="fill:url(#paint2_linear_1245_66459)"/>
<path d="m65.5 134.5v-68.865c0.8334 0.0861 1.9717 0.2211 3.3584 0.4273 3.1196 0.464 7.4889 1.2873 12.47 2.7228 9.9828 2.8767 22.316 8.1809 32.01 17.875 9.695 9.6942 14.999 22.027 17.875 32.01 1.436 4.982 2.259 9.351 2.723 12.471 0.206 1.386 0.341 2.525 0.428 3.358z" stroke="#000" stroke-opacity=".12" stroke-width="3"/> <path d="m65.5 134.5v-68.865c0.8334 0.0861 1.9717 0.2211 3.3584 0.4273 3.1196 0.464 7.4889 1.2873 12.47 2.7228 9.9828 2.8767 22.316 8.1809 32.01 17.875 9.695 9.6942 14.999 22.027 17.875 32.01 1.436 4.982 2.259 9.351 2.723 12.471 0.206 1.386 0.341 2.525 0.428 3.358z" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
</g><defs> </g><defs>
<pattern id="horizontal-liquid" width="172" height="72" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse"> <pattern id="liquid" width="172" height="72" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/> <circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/> <circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/> <circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
@ -415,37 +415,6 @@
<stop stop-color="#727171" offset=".89138"/> <stop stop-color="#727171" offset=".89138"/>
<stop stop-color="#727171" offset="1"/> <stop stop-color="#727171" offset="1"/>
</linearGradient> </linearGradient>
<pattern id="vertical-liquid" width="172" height="72" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
<circle transform="rotate(-90)" cx="-58" cy="34" r="8" fill="url(#paint31_linear_1182_32781-4)"/>
<circle transform="rotate(-90)" cx="-58" cy="155" r="8" fill="url(#paint32_linear_1182_32781-3)"/>
<circle transform="rotate(-90)" cx="-26" cy="33" r="5" fill="url(#paint33_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-26" cy="162" r="5" fill="url(#paint34_linear_1182_32781-4)"/>
<circle transform="rotate(-90)" cx="-37" cy="5" r="5" fill="url(#paint35_linear_1182_32781-6)"/>
<circle transform="rotate(-90)" cx="-8" cy="94" r="4" fill="url(#paint36_linear_1182_32781-9)"/>
<circle transform="rotate(-90)" cx="-60" cy="72" r="4" fill="url(#paint37_linear_1182_32781-4)"/>
<circle transform="rotate(-90)" cx="-37" cy="112" r="5" fill="url(#paint38_linear_1182_32781-2)"/>
<circle transform="rotate(-90)" cx="-39" cy="59" r="5" fill="url(#paint39_linear_1182_32781-2)"/>
<circle transform="rotate(-90)" cx="-62" cy="115" r="5" fill="url(#paint40_linear_1182_32781-6)"/>
<circle transform="rotate(-90)" cx="-42" cy="139" r="5" fill="url(#paint41_linear_1182_32781-4)"/>
<circle transform="rotate(-90)" cx="-21" cy="76" r="5" fill="url(#paint42_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-50.5" cy="126.5" r="2.5" fill="url(#paint43_linear_1182_32781-2)"/>
<circle transform="rotate(-90)" cx="-46.5" cy="169.5" r="2.5" fill="url(#paint44_linear_1182_32781-8)"/>
<circle transform="rotate(-90)" cx="-9.5" cy="57.5" r="2.5" fill="url(#paint45_linear_1182_32781-8)"/>
<circle transform="rotate(-90)" cx="-35.5" cy="96.5" r="2.5" fill="url(#paint46_linear_1182_32781-9)"/>
<circle transform="rotate(-90)" cx="-23.5" cy="91.5" r="2.5" fill="url(#paint47_linear_1182_32781-2)"/>
<circle transform="rotate(-90)" cx="-40.5" cy="22.5" r="2.5" fill="url(#paint48_linear_1182_32781-88)"/>
<circle transform="rotate(-90)" cx="-23.5" cy="124.5" r="2.5" fill="url(#paint49_linear_1182_32781-8)"/>
<circle transform="rotate(-90)" cx="-47.5" cy="86.5" r="2.5" fill="url(#paint50_linear_1182_32781-6)"/>
<circle transform="rotate(-90)" cx="-21.5" cy="51.5" r="2.5" fill="url(#paint51_linear_1182_32781-8)"/>
<circle transform="rotate(-90)" cx="-51.5" cy="48.5" r="2.5" fill="url(#paint52_linear_1182_32781-3)"/>
<circle transform="rotate(-90)" cx="-64" cy="14" r="4" fill="url(#paint53_linear_1182_32781-83)"/>
<circle transform="rotate(-90)" cx="-64" cy="135" r="4" fill="url(#paint54_linear_1182_32781-33)"/>
<circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/>
<path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/>
</pattern>
<clipPath id="clip0_1245_66459"> <clipPath id="clip0_1245_66459">
<rect width="200" height="200" fill="#fff"/> <rect width="200" height="200" fill="#fff"/>
</clipPath> </clipPath>
@ -530,12 +499,17 @@
<clipPath id="clipPath4208"> <clipPath id="clipPath4208">
<rect width="200" height="200" fill="#fff"/> <rect width="200" height="200" fill="#fff"/>
</clipPath> </clipPath>
<pattern id="center-liquid" patternTransform="scale(1.17) rotate(-45)" patternUnits="userSpaceOnUse" xlink:href="#horizontal-liquid"/> <pattern id="base-horizontal-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
</defs><rect x="14" y="64" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect transform="rotate(90)" x="136" y="-136" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect x="14" y="64" width="50" height="72" fill="url(#horizontal-liquid)" stroke-width="0" style="display: none;" tb:tag="horizontal-fluid"/><rect transform="rotate(90)" x="136" y="-136" width="50" height="72" fill="url(#vertical-liquid)" stroke-width="0" style="display: none;" tb:tag="vertical-fluid"/><path d="m64 136.15-0.1-72.05c0.87135 0.09 2.1615 0.23117 3.6113 0.44675 3.2616 0.48513 7.8299 1.3459 13.038 2.8468 10.437 3.0077 23.332 8.5533 33.468 18.689 10.136 10.136 15.682 23.03 18.689 33.467 1.5014 5.2088 2.3619 9.7767 2.847 13.039 0.21538 1.4491 0.35652 2.6909 0.44748 3.5618z" fill="#1ec1f4" stroke="#000" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><g transform="matrix(0 -.99929 .99918 0 .25495 199.91)" style="display: none;" tb:tag="center-fluid"> <pattern id="horizontal-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect x="-172" width="688" height="72" fill="url(#base-horizontal-liquid)" stroke-width="0"/></pattern>
<path d="m63.856 63.897 72.051-0.10008c-0.0901 0.87205-0.23133 2.1632-0.44707 3.6142-0.48547 3.2643-1.3469 7.8362-2.8488 13.048-3.0098 10.446-8.5594 23.351-18.702 33.495-10.143 10.145-23.046 15.695-33.491 18.704-5.2125 1.5026-9.7836 2.3638-13.048 2.8493-1.4501 0.21555-2.7428 0.3568-3.6144 0.44784z" fill="url(#center-liquid)" stroke="#000" stroke-width="0" style="" tb:tag="center-fluid-background"/> <pattern id="base-vertical-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<path d="m132.06 63.6s-0.0216 31.824-17.622 50.677c-17.228 18.043-50.883 17.636-50.883 17.636" fill="none" stroke="#727171" stroke-width="7.9" style=""/> <pattern id="vertical-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect x="-172" width="688" height="72" fill="url(#base-vertical-liquid)" stroke-width="0"/></pattern>
<path d="m63.955 63.797v-0.20016h7.8565v8.1064h-8.1567v-7.9062z" style="fill: rgb(114, 113, 113); stroke-width: 1.0035;"/> <pattern id="base-center-liquid" width="172" height="72" patternTransform="translate(15)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
</g><g transform="translate(-1e-4,3e-4)" style="display: none;" tb:tag="leak"> <pattern id="center-liquid" width="172" height="72" patternTransform="scale(1.17) rotate(135)" patternUnits="userSpaceOnUse"><rect x="-172" width="688" height="72" fill="url(#base-center-liquid)" stroke-width="0"/></pattern>
</defs><rect x="14" y="64" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect transform="rotate(90)" x="136" y="-136" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect x="14" y="64" width="50" height="72" fill="url(#horizontal-liquid)" stroke-width="0" style="display: none;" tb:tag="horizontal-fluid"/><rect transform="rotate(90)" x="136" y="-136" width="50" height="72" fill="url(#vertical-liquid)" stroke-width="0" style="display: none;" tb:tag="vertical-fluid"/><path d="m64 136 72.05 0.1c-0.09-0.87135-0.23117-2.1615-0.44675-3.6113-0.48513-3.2616-1.3459-7.8299-2.8468-13.038-3.0077-10.437-8.5533-23.332-18.689-33.468-10.136-10.136-23.03-15.682-33.467-18.689-5.2088-1.5014-9.7767-2.3619-13.039-2.847-1.4491-0.21538-2.6909-0.35652-3.5618-0.44748z" fill="#1ec1f4" stroke="#000" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><g transform="matrix(0 -.99929 .99919 0 .27925 199.93)" style="display: none;" tb:tag="center-fluid">
<path d="m63.956 63.95 72.051-0.10008c-0.0901 0.87205-0.23133 2.1632-0.44707 3.6142-0.48547 3.2643-1.3469 7.8362-2.8488 13.048-3.0098 10.446-8.5594 23.351-18.702 33.495-10.143 10.145-23.046 15.695-33.491 18.704-5.2125 1.5026-9.7836 2.3638-13.048 2.8493-1.4501 0.21555-2.7428 0.3568-3.6144 0.44784z" fill="url(#center-liquid)" stroke="#000" stroke-width="0" tb:tag="center-fluid-background" style=""/>
<path d="m132.05 63.75s-0.0216 31.824-17.622 50.677c-17.142 17.939-50.576 17.628-50.576 17.628" fill="none" stroke="#727171" stroke-width="7.9" style=""/>
<path d="m63.956 63.954v-0.15398h7.8942v8.05h-8.0503v-7.896z" style="fill: rgb(114, 113, 113);"/>
</g><g transform="rotate(-90 100 100)" style="display: none;" tb:tag="leak">
<path d="m62.784 80.767c0.417 0.1391 0.9517 0.2463 1.3259 0.4603 1.326 0.4603 2.5556 1.2628 3.3039 2.386 0.2779 0.5563 0.5129 1.1873 0.6731 1.7756 0.4915 1.5723 1.0257 3.0697 1.5599 4.5672 0.545 1.3049 1.0792 2.8023 0.544 4.0855-0.1925-0.0107-0.3101 0.0213-0.5026 0.0105 0.3307 2.3744 2.2423 0.7354 6.5024 7.0127 2.2452 8.828 4.8752 21.579 4.8212 23.236-0.0323 0.578 0.0531 1.123 0.0636 1.626-0.011 0.887-0.0969 1.732-0.3752 2.566-0.1499 0.61-0.4924 1.209-0.7172 1.775-0.6315 1.626-1.1134 3.337-1.253 5.144 0.3745-0.481 0.749-0.962 1.1234-1.443 0 0 0.353-0.096 0.2353-0.064-0.1069-0.161 0.0216-0.385 0.182-0.492 0.3527 0.599 0.7055 1.198 0.9833 1.754 1.6139 2.91 3.185 5.894 4.478 9.017-0.7155-3.091-1.8588-6.128-3.2374-9.102-0.3099-0.674-0.6626-1.273-0.8121-2.054-0.4378-1.839 0.4398-3.721 1.4243-5.443l0.0428-0.075c0.0856-0.149 0.214-0.374 0.4065-0.363 0.2354-0.064 0.4919 0.182 0.6737 0.385 0.8659 1.091 1.7746 2.108 2.6833 3.124 1.1653 1.262 2.459 2.3 3.5814 3.637 1.0156 1.177 1.8708 2.461 2.6511 3.702 0.3527 0.599 0.7482 1.123 0.8656 1.786 0.1923 0.706 0.192 1.401 0.0742 2.128-0.1287 0.92-0.2146 1.765-0.4181 2.642-1.0925 5.036 4.1863 14.098 2.2917 18.974 1.1129-1.946 2.1938-4.009 3.3067-5.955 0.4161 2.919 1.6132 5.69 2.7672 8.535 0.342 0.791 0.684 1.583 0.865 2.481 0.192 0.706 0.224 1.519 0.224 2.214 0 1.391-0.119 2.813-0.878 3.968 0.577-0.663 1.006-1.412 1.316-2.128 0.182 0.203 0.438 0.449 0.62 0.652 1.604 1.712 3.25 3.349 4.779 5.018-0.374-0.909-0.984-1.754-1.636-2.525-0.908-1.016-2.063-1.776-2.651-3.006-0.695-1.39-0.448-3.037-0.629-4.631-0.245-2.524-1.464-4.909-2.49-7.284-1.026-2.374-2.4215-4.17-3.795-8.188-0.9898-2.895-2.4972-7.611-1.9831-9.9 0.1179-0.727 0.3534-1.486 0.6638-2.203 0.182-0.492 0.4389-0.941 0.7385-1.465 0.3424-0.599 0.642-1.123 0.9096-1.764 0.2141-0.374 0.3532-0.791 0.3748-1.176 0.0643-0.46-0.1387-0.974-0.3097-1.369-0.577-1.423-1.1541-2.846-1.7311-4.268 0.0641 0.235 0.1602 0.588 0.2243 0.823 0.5127 1.883 0.9506 3.722 0.6933 5.562-0.0216 0.385-0.1607 0.802-0.4068 1.058-0.0428 0.075-0.0856 0.15-0.1605 0.107-0.3637 0.289-1.0909 0.171-1.1442-0.257-0.5668-0.224-1.112-0.834-1.4754-1.241-2.6726-3.316-5.3023-6.707-7.8572-10.055-0.2886-0.364-0.5773-0.728-0.7803-1.241-0.203-0.514-0.2563-0.942-0.2669-1.444-0.2982-3.647-2.4962-15.905-3.458-18.422-1.5388-4.028-0.5464-4.267-2.5593-5.9434 0.0535-0.2674-6.9427-5.6276-6.8891-5.8949 0.3322-1.7966 1.584-3.4646 2.7823-4.8651 1.2483-1.1574 5.2035 1.7887 5.8127 1.8117 0.6917 0.0262 13.721 15.05 14.606 16.356 0.3527 0.599 0.2189 1.786 0.4355 2.02 0.0765 0.083 1.5882 0.445 0.3927-0.21-0.023 0.609 1.7337 6.612 1.497 10.847-0.0216 0.385-0.0859 0.845-0.2251 1.262-0.0535 0.268-0.1819 0.492-0.3852 0.674-0.0428 0.075-0.1605 0.107-0.2781 0.139-0.1177 0.032-0.1497-0.086-0.2246-0.129-2.6943-2.236-5.2814-5.006-8.8318-5.746 0.1069 0.161 0.1817 0.204 0.2886 0.364 0.0749 0.043 0.1069 0.161 0.2566 0.246 1.3898 1.391 3.261 2.461 4.3728 3.991 0.5024 0.684 0.962 1.444 1.6677 1.947 0.9302 0.631 2.3097 0.824 3.0367 1.637 0.3635 0.407 0.6414 0.963 0.7695 1.433 0.3525 1.295 0.4269 2.728 0.897 3.99 0.2351 0.631 0.6627 1.273 0.8974 1.904 0.032 0.118 0.107 0.16 0.139 0.278 0.577 1.423 3.497 6.167 3.411 7.707 0.075-1.348-2.706-5.814-2.438-7.151 1.465 1.434 2.855 2.825 4.234 4.408-0.919-1.519-1.999-2.931-3.239-4.236-0.62-0.653-1.24-1.305-1.7-2.065-0.8443-1.476-0.6511-3.551 0.825-4.395 0.161-0.107 0.321-0.214 0.513-0.203 0.985-0.331 2.097-0.192 3.134-0.095 3.027 0.439 5.893 0.986 8.908 1.618 2.214 0.471 4.385 1.017 6.288 2.205 0.15 0.086 0.257 0.246 0.171 0.396 0.599-0.353 1.241-0.085 1.797 0.332 0.598 0.342 1.111 0.835 1.667 1.252 0.482 0.374 0.963 0.749 1.444 1.123 2.256 1.787 4.469 3.649 6.479 5.692-0.78-1.241-1.796-2.418-3.058-3.338-0.887-0.706-1.86-1.262-2.63-2.001-0.844-0.781-1.368-1.775-2.213-2.556-0.406-0.332-0.887-0.707-1.251-1.113 1.123-0.053 2.203-0.031 3.326-0.084-0.802-0.161-1.604-0.322-2.406-0.483-0.994-0.171-1.946-0.417-2.898-0.664-2.352-0.749-4.544-2.301-6.993-2.708-0.193-0.011-0.385-0.021-0.578-0.032 0.107 0.16 0.257 0.246 0.364 0.406-0.118 0.033-0.268-0.053-0.385-0.021-2.161-0.044-4.374-0.515-6.427-1.093-1.829-0.45-3.807-0.985-5.005-2.365-0.363-0.407-0.684-0.888-1.047-1.295-0.364-0.406-1.0054-0.674-1.5508-0.589 0.2575-2.534 1.0086-5.312-0.4558-7.441-1.1438-1.647-0.6523-3.686-2.1639-4.048 0.5862 0.632 0.9484-0.88 0.3852-2.121-0.5632-1.242-11.812-19.078-21.406-19.814-1.5082 0.7267-2.7386 2.0097-3.3594 3.4425-0.0428 0.0749-0.0428 0.0749-0.0856 0.1497-0.951-2.3317-1.9341-4.7812-3.2381-7.0168-0.4275-0.6418-0.8123-1.3585-1.4431-1.8186-1.2832-0.5352-2.898-0.6641-4.438-0.7501z" clip-path="url(#clipPath4310)" fill="#5c5a5a" style=""/> <path d="m62.784 80.767c0.417 0.1391 0.9517 0.2463 1.3259 0.4603 1.326 0.4603 2.5556 1.2628 3.3039 2.386 0.2779 0.5563 0.5129 1.1873 0.6731 1.7756 0.4915 1.5723 1.0257 3.0697 1.5599 4.5672 0.545 1.3049 1.0792 2.8023 0.544 4.0855-0.1925-0.0107-0.3101 0.0213-0.5026 0.0105 0.3307 2.3744 2.2423 0.7354 6.5024 7.0127 2.2452 8.828 4.8752 21.579 4.8212 23.236-0.0323 0.578 0.0531 1.123 0.0636 1.626-0.011 0.887-0.0969 1.732-0.3752 2.566-0.1499 0.61-0.4924 1.209-0.7172 1.775-0.6315 1.626-1.1134 3.337-1.253 5.144 0.3745-0.481 0.749-0.962 1.1234-1.443 0 0 0.353-0.096 0.2353-0.064-0.1069-0.161 0.0216-0.385 0.182-0.492 0.3527 0.599 0.7055 1.198 0.9833 1.754 1.6139 2.91 3.185 5.894 4.478 9.017-0.7155-3.091-1.8588-6.128-3.2374-9.102-0.3099-0.674-0.6626-1.273-0.8121-2.054-0.4378-1.839 0.4398-3.721 1.4243-5.443l0.0428-0.075c0.0856-0.149 0.214-0.374 0.4065-0.363 0.2354-0.064 0.4919 0.182 0.6737 0.385 0.8659 1.091 1.7746 2.108 2.6833 3.124 1.1653 1.262 2.459 2.3 3.5814 3.637 1.0156 1.177 1.8708 2.461 2.6511 3.702 0.3527 0.599 0.7482 1.123 0.8656 1.786 0.1923 0.706 0.192 1.401 0.0742 2.128-0.1287 0.92-0.2146 1.765-0.4181 2.642-1.0925 5.036 4.1863 14.098 2.2917 18.974 1.1129-1.946 2.1938-4.009 3.3067-5.955 0.4161 2.919 1.6132 5.69 2.7672 8.535 0.342 0.791 0.684 1.583 0.865 2.481 0.192 0.706 0.224 1.519 0.224 2.214 0 1.391-0.119 2.813-0.878 3.968 0.577-0.663 1.006-1.412 1.316-2.128 0.182 0.203 0.438 0.449 0.62 0.652 1.604 1.712 3.25 3.349 4.779 5.018-0.374-0.909-0.984-1.754-1.636-2.525-0.908-1.016-2.063-1.776-2.651-3.006-0.695-1.39-0.448-3.037-0.629-4.631-0.245-2.524-1.464-4.909-2.49-7.284-1.026-2.374-2.4215-4.17-3.795-8.188-0.9898-2.895-2.4972-7.611-1.9831-9.9 0.1179-0.727 0.3534-1.486 0.6638-2.203 0.182-0.492 0.4389-0.941 0.7385-1.465 0.3424-0.599 0.642-1.123 0.9096-1.764 0.2141-0.374 0.3532-0.791 0.3748-1.176 0.0643-0.46-0.1387-0.974-0.3097-1.369-0.577-1.423-1.1541-2.846-1.7311-4.268 0.0641 0.235 0.1602 0.588 0.2243 0.823 0.5127 1.883 0.9506 3.722 0.6933 5.562-0.0216 0.385-0.1607 0.802-0.4068 1.058-0.0428 0.075-0.0856 0.15-0.1605 0.107-0.3637 0.289-1.0909 0.171-1.1442-0.257-0.5668-0.224-1.112-0.834-1.4754-1.241-2.6726-3.316-5.3023-6.707-7.8572-10.055-0.2886-0.364-0.5773-0.728-0.7803-1.241-0.203-0.514-0.2563-0.942-0.2669-1.444-0.2982-3.647-2.4962-15.905-3.458-18.422-1.5388-4.028-0.5464-4.267-2.5593-5.9434 0.0535-0.2674-6.9427-5.6276-6.8891-5.8949 0.3322-1.7966 1.584-3.4646 2.7823-4.8651 1.2483-1.1574 5.2035 1.7887 5.8127 1.8117 0.6917 0.0262 13.721 15.05 14.606 16.356 0.3527 0.599 0.2189 1.786 0.4355 2.02 0.0765 0.083 1.5882 0.445 0.3927-0.21-0.023 0.609 1.7337 6.612 1.497 10.847-0.0216 0.385-0.0859 0.845-0.2251 1.262-0.0535 0.268-0.1819 0.492-0.3852 0.674-0.0428 0.075-0.1605 0.107-0.2781 0.139-0.1177 0.032-0.1497-0.086-0.2246-0.129-2.6943-2.236-5.2814-5.006-8.8318-5.746 0.1069 0.161 0.1817 0.204 0.2886 0.364 0.0749 0.043 0.1069 0.161 0.2566 0.246 1.3898 1.391 3.261 2.461 4.3728 3.991 0.5024 0.684 0.962 1.444 1.6677 1.947 0.9302 0.631 2.3097 0.824 3.0367 1.637 0.3635 0.407 0.6414 0.963 0.7695 1.433 0.3525 1.295 0.4269 2.728 0.897 3.99 0.2351 0.631 0.6627 1.273 0.8974 1.904 0.032 0.118 0.107 0.16 0.139 0.278 0.577 1.423 3.497 6.167 3.411 7.707 0.075-1.348-2.706-5.814-2.438-7.151 1.465 1.434 2.855 2.825 4.234 4.408-0.919-1.519-1.999-2.931-3.239-4.236-0.62-0.653-1.24-1.305-1.7-2.065-0.8443-1.476-0.6511-3.551 0.825-4.395 0.161-0.107 0.321-0.214 0.513-0.203 0.985-0.331 2.097-0.192 3.134-0.095 3.027 0.439 5.893 0.986 8.908 1.618 2.214 0.471 4.385 1.017 6.288 2.205 0.15 0.086 0.257 0.246 0.171 0.396 0.599-0.353 1.241-0.085 1.797 0.332 0.598 0.342 1.111 0.835 1.667 1.252 0.482 0.374 0.963 0.749 1.444 1.123 2.256 1.787 4.469 3.649 6.479 5.692-0.78-1.241-1.796-2.418-3.058-3.338-0.887-0.706-1.86-1.262-2.63-2.001-0.844-0.781-1.368-1.775-2.213-2.556-0.406-0.332-0.887-0.707-1.251-1.113 1.123-0.053 2.203-0.031 3.326-0.084-0.802-0.161-1.604-0.322-2.406-0.483-0.994-0.171-1.946-0.417-2.898-0.664-2.352-0.749-4.544-2.301-6.993-2.708-0.193-0.011-0.385-0.021-0.578-0.032 0.107 0.16 0.257 0.246 0.364 0.406-0.118 0.033-0.268-0.053-0.385-0.021-2.161-0.044-4.374-0.515-6.427-1.093-1.829-0.45-3.807-0.985-5.005-2.365-0.363-0.407-0.684-0.888-1.047-1.295-0.364-0.406-1.0054-0.674-1.5508-0.589 0.2575-2.534 1.0086-5.312-0.4558-7.441-1.1438-1.647-0.6523-3.686-2.1639-4.048 0.5862 0.632 0.9484-0.88 0.3852-2.121-0.5632-1.242-11.812-19.078-21.406-19.814-1.5082 0.7267-2.7386 2.0097-3.3594 3.4425-0.0428 0.0749-0.0428 0.0749-0.0856 0.1497-0.951-2.3317-1.9341-4.7812-3.2381-7.0168-0.4275-0.6418-0.8123-1.3585-1.4431-1.8186-1.2832-0.5352-2.898-0.6641-4.438-0.7501z" clip-path="url(#clipPath4310)" fill="#5c5a5a" style=""/>
<path d="m96.836 106.97c-0.1607 0.802-0.3972 1.217-0.1729 2.041 0.4486 1.647 1.0572 2.066 1.7411 3.649 0.6732 1.776 0.6698 2.683 0.7546 4.619 0.0211 1.005-0.0327 1.968-0.1614 2.887 0.7808-0.149 1.8421 1.436 2.3871 2.046 0.545 0.609 1.005 1.369 1.722 1.679 0.791 0.353 1.646 0.247 2.491 0.333 0.92 0.128 1.786 0.524 2.695 0.845 1.861 0.568 3.797 0.483 5.7 0.28-0.075-0.042-0.075-0.042-0.149-0.085-2.161-0.044-4.374-0.515-6.428-1.093-1.828-0.45-3.807-0.985-5.004-2.365-0.363-0.407-0.684-0.888-1.048-1.295-0.363-0.406-1.328-1.638-1.8737-1.553 0.2575-2.534 0.8497-4.297-0.615-6.426-1.1437-1.647-0.9919-2.182-1.5794-4.108-0.2138-0.321-0.374-0.909-0.4594-1.454z" clip-path="url(#clipPath4304)" fill="#8b8b8b" style=""/> <path d="m96.836 106.97c-0.1607 0.802-0.3972 1.217-0.1729 2.041 0.4486 1.647 1.0572 2.066 1.7411 3.649 0.6732 1.776 0.6698 2.683 0.7546 4.619 0.0211 1.005-0.0327 1.968-0.1614 2.887 0.7808-0.149 1.8421 1.436 2.3871 2.046 0.545 0.609 1.005 1.369 1.722 1.679 0.791 0.353 1.646 0.247 2.491 0.333 0.92 0.128 1.786 0.524 2.695 0.845 1.861 0.568 3.797 0.483 5.7 0.28-0.075-0.042-0.075-0.042-0.149-0.085-2.161-0.044-4.374-0.515-6.428-1.093-1.828-0.45-3.807-0.985-5.004-2.365-0.363-0.407-0.684-0.888-1.048-1.295-0.363-0.406-1.328-1.638-1.8737-1.553 0.2575-2.534 0.8497-4.297-0.615-6.426-1.1437-1.647-0.9919-2.182-1.5794-4.108-0.2138-0.321-0.374-0.909-0.4594-1.454z" clip-path="url(#clipPath4304)" fill="#8b8b8b" style=""/>
<path d="m87.799 115.57c0.1069 0.161 0.1818 0.204 0.2887 0.364 0.0748 0.043 0.1069 0.161 0.2566 0.246l0.0748 0.043c0.5667 0.225 1.1335 0.449 1.6895 0.867 0.4491 0.257 0.9302 0.631 1.3365 0.963 1.7748 1.412 3.2928 3.273 5.3993 4.279 0.2997-0.523 0.5993-1.047 0.7064-1.582-0.0535 0.267-0.182 0.492-0.3852 0.674-0.0428 0.074-0.1605 0.106-0.2781 0.138-0.1177 0.033-0.1497-0.085-0.2246-0.128-2.6835-2.429-5.3135-5.125-8.8639-5.864z" clip-path="url(#clipPath4298)" fill="#8b8b8b" style=""/> <path d="m87.799 115.57c0.1069 0.161 0.1818 0.204 0.2887 0.364 0.0748 0.043 0.1069 0.161 0.2566 0.246l0.0748 0.043c0.5667 0.225 1.1335 0.449 1.6895 0.867 0.4491 0.257 0.9302 0.631 1.3365 0.963 1.7748 1.412 3.2928 3.273 5.3993 4.279 0.2997-0.523 0.5993-1.047 0.7064-1.582-0.0535 0.267-0.182 0.492-0.3852 0.674-0.0428 0.074-0.1605 0.106-0.2781 0.138-0.1177 0.033-0.1497-0.085-0.2246-0.128-2.6835-2.429-5.3135-5.125-8.8639-5.864z" clip-path="url(#clipPath4298)" fill="#8b8b8b" style=""/>

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 49 KiB

15
application/src/main/data/json/system/scada_symbols/left-flow-meter.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="400" fill="none" version="1.1" viewBox="0 0 400 400"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="400" fill="none" version="1.1" viewBox="0 0 400 400"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Left flow meter", "title": "Left flow meter",
"description": "Left flow meter component used to display flow related value and render various states. Includes pipe fluid and leak visualizations.", "description": "Left flow meter component used to display flow related value and render various states. Includes pipe fluid and leak visualizations.",
"searchTags": [ "searchTags": [
@ -20,7 +19,7 @@
}, },
{ {
"tag": "border", "tag": "border",
"stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}", "stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (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}",
"actions": { "actions": {
"click": { "click": {
"actionFunction": "ctx.api.callAction(event, 'displayClick');" "actionFunction": "ctx.api.callAction(event, 'displayClick');"
@ -38,7 +37,7 @@
}, },
{ {
"tag": "fluid", "tag": "fluid",
"stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill');\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(ctx.values.flowAnimationSpeed);\n }\n} else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}", "stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill').first();\n\nvar fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(ctx.values.flowAnimationSpeed);\n }\n }\n} else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}",
"actions": null "actions": null
}, },
{ {
@ -733,7 +732,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><mask id="path-7-inside-1_1253_89545" fill="white"> }]]></tb:metadata>
<mask id="path-7-inside-1_1253_89545" fill="white">
<path d="m325 125c0 69.036-55.964 125-125 125s-125-55.964-125-125 55.964-125 125-125 125 55.964 125 125zm-236.01 0c0 61.306 49.699 111 111.01 111s111-49.699 111-111c0-61.306-49.699-111.01-111-111.01s-111.01 49.699-111.01 111.01z"/> <path d="m325 125c0 69.036-55.964 125-125 125s-125-55.964-125-125 55.964-125 125-125 125 55.964 125 125zm-236.01 0c0 61.306 49.699 111 111.01 111s111-49.699 111-111c0-61.306-49.699-111.01-111-111.01s-111.01 49.699-111.01 111.01z"/>
</mask><defs> </mask><defs>
<linearGradient id="paint2_linear_1253_89545" x1="155.2" x2="177.6" y1="61" y2="85.4" gradientTransform="translate(-75,75)" gradientUnits="userSpaceOnUse"> <linearGradient id="paint2_linear_1253_89545" x1="155.2" x2="177.6" y1="61" y2="85.4" gradientTransform="translate(-75,75)" gradientUnits="userSpaceOnUse">
@ -888,7 +888,7 @@
<stop stop-color="#727171" stop-opacity=".35" offset=".71855"/> <stop stop-color="#727171" stop-opacity=".35" offset=".71855"/>
<stop stop-color="#727171" offset="1"/> <stop stop-color="#727171" offset="1"/>
</linearGradient> </linearGradient>
<pattern id="liquid" width="172" height="72" patternTransform="translate(14,24)" patternUnits="userSpaceOnUse"> <pattern id="baseLiquid" width="172" height="72" patternTransform="translate(14,24)" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/> <circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/> <circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/> <circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
@ -919,7 +919,8 @@
<circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/> <circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/>
<path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/> <path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/>
</pattern> </pattern>
</defs><rect transform="rotate(-90)" x="-386" y="264" width="372" height="72" fill="#fff" tb:tag="pipe-background"/><rect transform="rotate(-90)" x="-386" y="264" width="372" height="72" fill="url(#paint0_linear_1310_41909)" style="fill:url(#paint0_linear_1310_41909)"/><rect transform="rotate(-90)" x="-384.5" y="265.5" width="369" height="69" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="rotate(90)" x="14" y="-336" width="372" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect transform="rotate(90)" x="14" y="-336" width="372" height="72" fill="url(#liquid)" stroke-width="0" style="display: none;" tb:tag="fluid"/><path d="m236 164h28.421l31.579 36-31.579 36h-28.421z" fill="#fff" tb:tag="pipe-background"/><path d="m236 164h28.421l31.579 36-31.579 36h-28.421z" fill="url(#paint1_linear_1310_41909)" style="fill:url(#paint1_linear_1310_41909)"/><path d="m237.5 165.5h26.242l30.263 34.5-30.263 34.5h-26.242z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="rotate(-90)" x="-398.5" y="251.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><rect transform="rotate(-90)" x="-12.5" y="251.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><path transform="translate(-75,75)" d="m325 125c0 69.036-55.964 125-125 125s-125-55.964-125-125 55.964-125 125-125 125 55.964 125 125zm-236.01 0c0 61.306 49.699 111 111.01 111s111-49.699 111-111c0-61.306-49.699-111.01-111-111.01s-111.01 49.699-111.01 111.01z" fill="#4a4848" mask="url(#path-7-inside-1_1253_89545)" stroke="#727171" stroke-width="6" tb:tag="border"/><circle cx="125" cy="200" r="111" fill="#fff" tb:tag="background"/><rect x="44.5" y="180.5" width="161" height="39" rx="3.8571" fill="#4a4848" fill-opacity=".06" tb:tag="border"/><path d="m48.36 179c-2.9419 0-5.3574 2.4155-5.3574 5.3574v31.285c0 2.9419 2.4155 5.3574 5.3574 5.3574h153.29c2.9419 0 5.3574-2.4155 5.3574-5.3574v-31.285c0-2.9419-2.4155-5.3574-5.3574-5.3574zm0 3h153.29c1.3318 0 2.3574 1.0256 2.3574 2.3574v31.285c0 1.3318-1.0256 2.3574-2.3574 2.3574h-153.29c-1.3318 0-2.3574-1.0256-2.3574-2.3574v-31.285c0-1.3318 1.0256-2.3574 2.3574-2.3574z" fill="#4a4848" tb:tag="border"/><circle cx="91" cy="148" r="16" fill="url(#paint2_linear_1253_89545)" style="fill:url(#paint2_linear_1253_89545)"/><circle cx="91" cy="148" r="15.5" stroke="#000" stroke-opacity=".12"/><circle cx="159" cy="148" r="16" fill="url(#paint3_linear_1253_89545)" style="fill:url(#paint3_linear_1253_89545)"/><circle cx="159" cy="148" r="15.5" stroke="#000" stroke-opacity=".12"/><text x="123.96094" y="201.94531" dominant-baseline="middle" fill="#000000" fill-opacity=".87" text-anchor="middle" style="font-family:Roboto;font-size:32px;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-weight:500" tb:tag="value" xml:space="preserve"><tspan>0</tspan></text><text x="124.47363" y="249.4729" dominant-baseline="middle" fill="#000000" fill-opacity=".87" text-anchor="middle" style="font-family:Roboto;font-size:22px;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-weight:500" tb:tag="valueUnits" xml:space="preserve"><tspan>m³/hr</tspan></text><g transform="translate(-75,75.001)" style="display: none;" tb:tag="broken"> <pattern id="liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#baseLiquid)"/></pattern>
</defs><rect transform="rotate(-90)" x="-386" y="264" width="372" height="72" fill="#fff" tb:tag="pipe-background"/><rect transform="rotate(-90)" x="-386" y="264" width="372" height="72" fill="url(#paint0_linear_1310_41909)" style="fill:url(#paint0_linear_1310_41909)"/><rect transform="rotate(-90)" x="-384.5" y="265.5" width="369" height="69" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="rotate(90)" x="14" y="-336" width="372" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect transform="rotate(90)" x="14" y="-336" width="372" height="72" fill="url(#liquid)" stroke-width="0" style="display: none;" tb:tag="fluid"/><path d="m236 164h28.421l31.579 36-31.579 36h-28.421z" fill="#fff" tb:tag="pipe-background"/><path d="m236 164h28.421l31.579 36-31.579 36h-28.421z" fill="url(#paint1_linear_1310_41909)" style="fill:url(#paint1_linear_1310_41909)"/><path d="m237.5 165.5h26.242l30.263 34.5-30.263 34.5h-26.242z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="rotate(-90)" x="-398.5" y="251.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><rect transform="rotate(-90)" x="-12.5" y="251.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><path transform="translate(-75,75)" d="m325 125c0 69.036-55.964 125-125 125s-125-55.964-125-125 55.964-125 125-125 125 55.964 125 125zm-236.01 0c0 61.306 49.699 111 111.01 111s111-49.699 111-111c0-61.306-49.699-111.01-111-111.01s-111.01 49.699-111.01 111.01z" fill="#4a4848" mask="url(#path-7-inside-1_1253_89545)" stroke="#727171" stroke-width="6" tb:tag="border"/><circle cx="125" cy="200" r="111" fill="#fff" tb:tag="background"/><rect x="44.5" y="180.5" width="161" height="39" rx="3.8571" fill="#4a4848" fill-opacity=".06" tb:tag="border"/><path d="m48.36 179c-2.9419 0-5.3574 2.4155-5.3574 5.3574v31.285c0 2.9419 2.4155 5.3574 5.3574 5.3574h153.29c2.9419 0 5.3574-2.4155 5.3574-5.3574v-31.285c0-2.9419-2.4155-5.3574-5.3574-5.3574zm0 3h153.29c1.3318 0 2.3574 1.0256 2.3574 2.3574v31.285c0 1.3318-1.0256 2.3574-2.3574 2.3574h-153.29c-1.3318 0-2.3574-1.0256-2.3574-2.3574v-31.285c0-1.3318 1.0256-2.3574 2.3574-2.3574z" fill="#4a4848" tb:tag="border"/><circle cx="91" cy="148" r="16" fill="url(#paint2_linear_1253_89545)" style="fill:url(#paint2_linear_1253_89545)"/><circle cx="91" cy="148" r="15.5" stroke="#000" stroke-opacity=".12"/><circle cx="159" cy="148" r="16" fill="url(#paint3_linear_1253_89545)" style="fill:url(#paint3_linear_1253_89545)"/><circle cx="159" cy="148" r="15.5" stroke="#000" stroke-opacity=".12"/><text x="123.96094" y="201.94531" fill="#000000" fill-opacity=".87" style="font-family:Roboto;font-size:32px;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-weight:500" tb:tag="value" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">0</tspan></text><text x="124.47363" y="249.4729" fill="#000000" fill-opacity=".87" style="font-family:Roboto;font-size:22px;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-weight:500" tb:tag="valueUnits" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">m³/hr</tspan></text><g transform="translate(-75,75.001)" style="display: none;" tb:tag="broken">
<g filter="url(#filter0_b_1281_42354)" style=""> <g filter="url(#filter0_b_1281_42354)" style="">
<circle cx="200" cy="125" r="111" fill="#000" fill-opacity=".24" style=""/> <circle cx="200" cy="125" r="111" fill="#000" fill-opacity=".24" style=""/>
</g> </g>

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 73 KiB

12
application/src/main/data/json/system/scada_symbols/left-heat-pump.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="800" height="600" fill="none" version="1.1" viewBox="0 0 800 600"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="800" height="600" fill="none" version="1.1" viewBox="0 0 800 600"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Left heat pump", "title": "Left heat pump",
"description": "Left heat pump with configurable connectors, running animation and various states.", "description": "Left heat pump with configurable connectors, running animation and various states.",
"searchTags": [ "searchTags": [
@ -26,12 +25,12 @@
}, },
{ {
"tag": "fan", "tag": "fan",
"stateRenderFunction": "var running = ctx.values.running;\nvar t = element.timeline();\nif (running) {\n if (!t.active()) {\n if (t.time()) {\n t.play();\n } else {\n ctx.api.animate(element, 2000).ease('-').rotate(360).loop();\n t = element.timeline();\n }\n }\n var speed = ctx.values.rotationAnimationSpeed;\n t.speed(speed);\n} else {\n t.pause();\n}\n", "stateRenderFunction": "var running = ctx.values.running;\nvar speed = ctx.values.rotationAnimationSpeed;\nvar fanRotate = ctx.api.cssAnimation(element);\nif (running) {\n if (!fanRotate) {\n fanRotate = ctx.api.cssAnimate(element, 2000)\n .rotate(360).loop().speed(speed);\n } else {\n fanRotate.speed(speed).play();\n }\n} else {\n if (fanRotate) {\n fanRotate.pause();\n }\n}\n",
"actions": null "actions": null
}, },
{ {
"tag": "fan-blade", "tag": "fan-blade",
"stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}", "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (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}",
"actions": null "actions": null
}, },
{ {
@ -673,14 +672,15 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><rect x="250" y="571" width="300" height="29" rx="7" fill="#fff"/><rect x="250" y="571" width="300" height="29" rx="7" fill="url(#paint0_linear_1826_356092)" style="fill:url(#paint0_linear_1826_356092)"/><rect x="251.5" y="572.5" width="297" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m303 545h194l32 26h-258z" fill="#fff"/><path d="m303 545h194l32 26h-258z" fill="url(#paint1_linear_1826_356092)" style="fill:url(#paint1_linear_1826_356092)"/><path d="m275.22 569.5 28.308-23h192.93l28.308 23z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="37" width="763" height="545" rx="12" fill="#e5e5e5"/><rect x="37" width="763" height="545" rx="12" fill="url(#paint2_linear_1826_356092)" style="fill:url(#paint2_linear_1826_356092)"/><rect x="38.5" y="1.5" width="760" height="542" rx="10.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m37 12c0-6.6274 5.3726-12 12-12h739c6.627 0 12 5.3726 12 12v521c0 6.627-5.373 12-12 12h-739c-6.6274 0-12-5.373-12-12z" fill="#4a4848" fill-opacity=".5"/><rect x="1.5" y="51.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><path d="m14 64h658v72h-658z" fill="#fff" tb:tag="pipe-background"/><path d="m14 64h658v72h-658z" fill="url(#paint17_linear_1826_356092)" style="fill:url(#paint17_linear_1826_356092)"/><path d="m15.5 65.5h655v69h-655z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="1.5" y="251.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><path d="m14 264h82v72h-82z" fill="#fff" tb:tag="pipe-background"/><path d="m14 264h82v72h-82z" fill="url(#paint25_linear_1826_356092)" style="fill:url(#paint25_linear_1826_356092)"/><path d="m15.5 265.5h79v69h-79z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="37" width="763" height="545" rx="12" fill="#e5e5e5" tb:tag="background"/><rect x="37" width="763" height="545" rx="12" fill="url(#paint2_linear_1826_356092)" style="fill:url(#paint2_linear_1826_356092)"/><rect x="38.5" y="1.5" width="760" height="542" rx="10.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><g filter="url(#filter0_d_1826_356092)"> }]]></tb:metadata>
<rect x="250" y="571" width="300" height="29" rx="7" fill="#fff"/><rect x="250" y="571" width="300" height="29" rx="7" fill="url(#paint0_linear_1826_356092)" style="fill:url(#paint0_linear_1826_356092)"/><rect x="251.5" y="572.5" width="297" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m303 545h194l32 26h-258z" fill="#fff"/><path d="m303 545h194l32 26h-258z" fill="url(#paint1_linear_1826_356092)" style="fill:url(#paint1_linear_1826_356092)"/><path d="m275.22 569.5 28.308-23h192.93l28.308 23z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="37" width="763" height="545" rx="12" fill="#e5e5e5"/><rect x="37" width="763" height="545" rx="12" fill="url(#paint2_linear_1826_356092)" style="fill:url(#paint2_linear_1826_356092)"/><rect x="38.5" y="1.5" width="760" height="542" rx="10.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m37 12c0-6.6274 5.3726-12 12-12h739c6.627 0 12 5.3726 12 12v521c0 6.627-5.373 12-12 12h-739c-6.6274 0-12-5.373-12-12z" fill="#4a4848" fill-opacity=".5"/><rect x="1.5" y="51.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><path d="m14 64h658v72h-658z" fill="#fff" tb:tag="pipe-background"/><path d="m14 64h658v72h-658z" fill="url(#paint17_linear_1826_356092)" style="fill:url(#paint17_linear_1826_356092)"/><path d="m15.5 65.5h655v69h-655z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="1.5" y="251.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><path d="m14 264h82v72h-82z" fill="#fff" tb:tag="pipe-background"/><path d="m14 264h82v72h-82z" fill="url(#paint25_linear_1826_356092)" style="fill:url(#paint25_linear_1826_356092)"/><path d="m15.5 265.5h79v69h-79z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="37" width="763" height="545" rx="12" fill="#e5e5e5" tb:tag="background"/><rect x="37" width="763" height="545" rx="12" fill="url(#paint2_linear_1826_356092)" style="fill:url(#paint2_linear_1826_356092)"/><rect x="38.5" y="1.5" width="760" height="542" rx="10.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><g filter="url(#filter0_d_1826_356092)">
<path d="m37 12c0-6.6274 5.3726-12 12-12h739c6.627 0 12 5.3726 12 12v521c0 6.627-5.373 12-12 12h-161.7c-0.532 0-1.002-0.027-1.529-0.1-13.196-1.836-202.34-29.137-319.77-101.9-151.45-93.843-252.83-258.48-266.44-281.34-1.0718-1.801-1.5648-3.765-1.5648-5.86z" fill="#e5e5e5" tb:tag="background"/> <path d="m37 12c0-6.6274 5.3726-12 12-12h739c6.627 0 12 5.3726 12 12v521c0 6.627-5.373 12-12 12h-161.7c-0.532 0-1.002-0.027-1.529-0.1-13.196-1.836-202.34-29.137-319.77-101.9-151.45-93.843-252.83-258.48-266.44-281.34-1.0718-1.801-1.5648-3.765-1.5648-5.86z" fill="#e5e5e5" tb:tag="background"/>
<path d="m37 12c0-6.6274 5.3726-12 12-12h739c6.627 0 12 5.3726 12 12v521c0 6.627-5.373 12-12 12h-161.7c-0.532 0-1.002-0.027-1.529-0.1-13.196-1.836-202.34-29.137-319.77-101.9-151.45-93.843-252.83-258.48-266.44-281.34-1.0718-1.801-1.5648-3.765-1.5648-5.86z" fill="url(#paint2_linear_1826_356092)" style="fill:url(#paint2_linear_1826_356092)"/> <path d="m37 12c0-6.6274 5.3726-12 12-12h739c6.627 0 12 5.3726 12 12v521c0 6.627-5.373 12-12 12h-161.7c-0.532 0-1.002-0.027-1.529-0.1-13.196-1.836-202.34-29.137-319.77-101.9-151.45-93.843-252.83-258.48-266.44-281.34-1.0718-1.801-1.5648-3.765-1.5648-5.86z" fill="url(#paint2_linear_1826_356092)" style="fill:url(#paint2_linear_1826_356092)"/>
<path d="m38.5 12c0-5.799 4.701-10.5 10.5-10.5h739c5.799 0 10.5 4.701 10.5 10.5v521c0 5.799-4.701 10.5-10.5 10.5h-161.7c-0.475 0-0.875-0.024-1.322-0.086-6.567-0.913-57.094-8.188-120.64-24.32-63.568-16.138-140.03-41.11-198.55-77.369-151.12-93.639-252.35-258-265.94-280.83-0.922-1.549-1.3538-3.244-1.3538-5.093z" stroke="#000" stroke-opacity=".12" stroke-width="3"/> <path d="m38.5 12c0-5.799 4.701-10.5 10.5-10.5h739c5.799 0 10.5 4.701 10.5 10.5v521c0 5.799-4.701 10.5-10.5 10.5h-161.7c-0.475 0-0.875-0.024-1.322-0.086-6.567-0.913-57.094-8.188-120.64-24.32-63.568-16.138-140.03-41.11-198.55-77.369-151.12-93.639-252.35-258-265.94-280.83-0.922-1.549-1.3538-3.244-1.3538-5.093z" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
</g><g filter="url(#filter2_ii_1826_356092)"> </g><g filter="url(#filter2_ii_1826_356092)">
<path d="m124 82c0-6.6274 5.373-12 12-12h157c6.627 0 12 5.3726 12 12v56c0 6.627-5.373 12-12 12h-157c-6.627 0-12-5.373-12-12z" fill="#fff" tb:tag="value-box-background"/> <path d="m124 82c0-6.6274 5.373-12 12-12h157c6.627 0 12 5.3726 12 12v56c0 6.627-5.373 12-12 12h-157c-6.627 0-12-5.373-12-12z" fill="#fff" tb:tag="value-box-background"/>
<path d="m125 82c0-6.0751 4.925-11 11-11h157c6.075 0 11 4.9249 11 11v56c0 6.075-4.925 11-11 11h-157c-6.075 0-11-4.925-11-11z" stroke="#fff" stroke-width="2"/> <path d="m125 82c0-6.0751 4.925-11 11-11h157c6.075 0 11 4.9249 11 11v56c0 6.075-4.925 11-11 11h-157c-6.075 0-11-4.925-11-11z" stroke="#fff" stroke-width="2"/>
<text x="214.69531" y="113.5625" dominant-baseline="middle" fill="#000000" font-family="Roboto" font-size="40px" font-weight="500" text-anchor="middle" tb:tag="value-text" xml:space="preserve"><tspan>27</tspan></text> <text x="214.69531" y="113.5625" fill="#000000" font-family="Roboto" font-size="40px" font-weight="500" tb:tag="value-text" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">27</tspan></text>
</g><circle cx="530" cy="280.2" r="180" fill="#b6b4b4"/><circle cx="530" cy="280.2" r="180" fill="url(#paint34_radial_1826_356092)" style="fill:url(#paint34_radial_1826_356092)"/><circle cx="530" cy="280.2" r="178.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><g transform="translate(349.75 99.867)" tb:tag="fan"> </g><circle cx="530" cy="280.2" r="180" fill="#b6b4b4"/><circle cx="530" cy="280.2" r="180" fill="url(#paint34_radial_1826_356092)" style="fill:url(#paint34_radial_1826_356092)"/><circle cx="530" cy="280.2" r="178.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><g transform="translate(349.75 99.867)" tb:tag="fan">
<circle cx="180" cy="180.2" r="180" fill="#b6b4b4"/> <circle cx="180" cy="180.2" r="180" fill="#b6b4b4"/>
<circle cx="180" cy="180.2" r="178.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/> <circle cx="180" cy="180.2" r="178.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/>

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

2
application/src/main/data/json/system/scada_symbols/left-motor-pump.svg

@ -10,7 +10,7 @@
"tags": [ "tags": [
{ {
"tag": "background", "tag": "background",
"stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}", "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (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}",
"actions": null "actions": null
}, },
{ {

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

17
application/src/main/data/json/system/scada_symbols/left-tee-pipe.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Left tee pipe", "title": "Left tee pipe",
"description": "Left tee pipe with configurable left/top/bottom fluid and leak visualizations.", "description": "Left tee pipe with configurable left/top/bottom fluid and leak visualizations.",
"searchTags": [ "searchTags": [
@ -8,7 +7,7 @@
], ],
"widgetSizeX": 1, "widgetSizeX": 1,
"widgetSizeY": 1, "widgetSizeY": 1,
"stateRenderFunction": "var leftLiquidPattern = prepareLiquidPattern('left-fluid');\nvar topLiquidPattern = prepareLiquidPattern('top-fluid');\nvar bottomLiquidPattern = prepareLiquidPattern('bottom-fluid');\n\nupdateLiquidPatternAnimation(leftLiquidPattern, 'left');\nupdateLiquidPatternAnimation(topLiquidPattern, 'top');\nupdateLiquidPatternAnimation(bottomLiquidPattern, 'bottom');\n\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill');\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, prefix) {\n if (liquidPattern) {\n var fluid = ctx.values[prefix + 'Fluid'] && !ctx.values.leak;\n var flow = ctx.values[prefix + 'Flow'];\n var flowDirection = ctx.values[prefix + 'FlowDirection'];\n var flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(flowAnimationSpeed);\n }\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n}\n\n", "stateRenderFunction": "var leftLiquidPattern = prepareLiquidPattern('left-fluid');\nvar topLiquidPattern = prepareLiquidPattern('top-fluid');\nvar bottomLiquidPattern = prepareLiquidPattern('bottom-fluid');\n\nupdateLiquidPatternAnimation(leftLiquidPattern, 'left');\nupdateLiquidPatternAnimation(topLiquidPattern, 'top');\nupdateLiquidPatternAnimation(bottomLiquidPattern, 'bottom');\n\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill').first();\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, prefix) {\n if (liquidPattern) {\n var fluid = ctx.values[prefix + 'Fluid'] && !ctx.values.leak;\n var flow = ctx.values[prefix + 'Flow'];\n var flowDirection = ctx.values[prefix + 'FlowDirection'];\n var flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n var fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(flowAnimationSpeed);\n }\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}\n",
"tags": [ "tags": [
{ {
"tag": "bottom-fluid", "tag": "bottom-fluid",
@ -576,7 +575,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><g clip-path="url(#clip0_1245_66573)"> }]]></tb:metadata>
<g clip-path="url(#clip0_1245_66573)">
<path d="m64 186v-172h72v172z" fill="#fff" tb:tag="pipe-background"/> <path d="m64 186v-172h72v172z" fill="#fff" tb:tag="pipe-background"/>
<path d="m64 186v-172h72v172z" fill="url(#paint0_linear_1245_66573)" style="fill:url(#paint0_linear_1245_66442)"/> <path d="m64 186v-172h72v172z" fill="url(#paint0_linear_1245_66573)" style="fill:url(#paint0_linear_1245_66442)"/>
<path d="m65.5 184.5v-169h69v169z" stroke="#000" stroke-opacity=".12" stroke-width="3"/> <path d="m65.5 184.5v-169h69v169z" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
@ -756,9 +756,12 @@
<stop stop-color="#727171" offset=".89138"/> <stop stop-color="#727171" offset=".89138"/>
<stop stop-color="#727171" offset="1"/> <stop stop-color="#727171" offset="1"/>
</linearGradient> </linearGradient>
<pattern id="left-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/> <pattern id="base-left-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="top-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/> <pattern id="left-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#base-left-liquid)"/></pattern>
<pattern id="bottom-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/> <pattern id="base-top-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="top-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#base-top-liquid)"/></pattern>
<pattern id="base-bottom-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="bottom-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#base-bottom-liquid)"/></pattern>
<clipPath id="clip0_1245_66573"> <clipPath id="clip0_1245_66573">
<rect width="200" height="200" fill="#fff"/> <rect width="200" height="200" fill="#fff"/>
</clipPath> </clipPath>

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 58 KiB

48
application/src/main/data/json/system/scada_symbols/left-top-elbow-pipe.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Left top elbow pipe", "title": "Left top elbow pipe",
"description": "Left top elbow pipe with fluid and leak visualizations.", "description": "Left top elbow pipe with fluid and leak visualizations.",
"searchTags": [ "searchTags": [
@ -8,7 +7,7 @@
], ],
"widgetSizeX": 1, "widgetSizeX": 1,
"widgetSizeY": 1, "widgetSizeY": 1,
"stateRenderFunction": "var centerLiquidPattern = prepareLiquidPattern('center-fluid-background');\nvar horizontalLiquidPattern = prepareLiquidPattern('horizontal-fluid');\nvar verticalLiquidPattern = prepareLiquidPattern('vertical-fluid');\n\nvar fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\nvar flowAnimationSpeed = ctx.values.flowAnimationSpeed;\n \nif (horizontalLiquidPattern) {\n updateLiquidPatternAnimation(horizontalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (verticalLiquidPattern) {\n updateLiquidPatternAnimation(verticalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (centerLiquidPattern) {\n updateLiquidPatternAnimation(centerLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, true);\n}\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill');\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, fluid, flow, flowDirection, flowAnimationSpeed, center) {\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection, center);\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection, center);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(flowAnimationSpeed);\n }\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse, center) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n if (center) {\n var delta = deltaX * 1.17 * Math.cos(45*Math.PI/180);\n liquidPattern.animate(1000).ease('-').relative(delta, -delta).loop();\n } else {\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}\n\n", "stateRenderFunction": "var centerLiquidPattern = prepareLiquidPattern('center-fluid-background');\nvar horizontalLiquidPattern = prepareLiquidPattern('horizontal-fluid');\nvar verticalLiquidPattern = prepareLiquidPattern('vertical-fluid');\n\nvar fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\nvar flowAnimationSpeed = ctx.values.flowAnimationSpeed;\n \nif (horizontalLiquidPattern) {\n updateLiquidPatternAnimation(horizontalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (verticalLiquidPattern) {\n updateLiquidPatternAnimation(verticalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (centerLiquidPattern) {\n updateLiquidPatternAnimation(centerLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, true);\n}\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill').first();\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, fluid, flow, flowDirection, flowAnimationSpeed, center) {\n \n var fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n liquidPattern.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection, center);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection, center);\n }\n if (flow && fluidAnimation) {\n fluidAnimation.speed(flowAnimationSpeed);\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse, center) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n if (center) {\n var duration = 1000 * 1.17;\n return ctx.api.cssAnimate(liquidPattern, duration).relative(deltaX, 0).loop();\n } else {\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n }\n}",
"tags": [ "tags": [
{ {
"tag": "center-fluid", "tag": "center-fluid",
@ -251,7 +250,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><g transform="rotate(90,100,100)" clip-path="url(#clip0_1245_66459)"> }]]></tb:metadata>
<g transform="rotate(90,100,100)" clip-path="url(#clip0_1245_66459)">
<rect x="14" y="64" width="50" height="72" fill="#fff" tb:tag="pipe-background"/> <rect x="14" y="64" width="50" height="72" fill="#fff" tb:tag="pipe-background"/>
<rect x="14" y="64" width="50" height="72" fill="url(#paint0_linear_1245_66459)" style="fill:url(#paint0_linear_1245_66459)"/> <rect x="14" y="64" width="50" height="72" fill="url(#paint0_linear_1245_66459)" style="fill:url(#paint0_linear_1245_66459)"/>
<rect x="15.5" y="65.5" width="47" height="69" stroke="#000" stroke-opacity=".12" stroke-width="3"/> <rect x="15.5" y="65.5" width="47" height="69" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
@ -264,7 +264,7 @@
<path d="m64 64s30.518 1.7177 50.4 21.6c19.882 19.882 21.6 50.4 21.6 50.4h-72z" fill="url(#paint2_linear_1245_66459)" style="fill:url(#paint2_linear_1245_66459)"/> <path d="m64 64s30.518 1.7177 50.4 21.6c19.882 19.882 21.6 50.4 21.6 50.4h-72z" fill="url(#paint2_linear_1245_66459)" style="fill:url(#paint2_linear_1245_66459)"/>
<path d="m65.5 134.5v-68.865c0.8334 0.0861 1.9717 0.2211 3.3584 0.4273 3.1196 0.464 7.4889 1.2873 12.47 2.7228 9.9828 2.8767 22.316 8.1809 32.01 17.875 9.695 9.6942 14.999 22.027 17.875 32.01 1.436 4.982 2.259 9.351 2.723 12.471 0.206 1.386 0.341 2.525 0.428 3.358z" stroke="#000" stroke-opacity=".12" stroke-width="3"/> <path d="m65.5 134.5v-68.865c0.8334 0.0861 1.9717 0.2211 3.3584 0.4273 3.1196 0.464 7.4889 1.2873 12.47 2.7228 9.9828 2.8767 22.316 8.1809 32.01 17.875 9.695 9.6942 14.999 22.027 17.875 32.01 1.436 4.982 2.259 9.351 2.723 12.471 0.206 1.386 0.341 2.525 0.428 3.358z" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
</g><defs> </g><defs>
<pattern id="horizontal-liquid" width="172" height="72" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse"> <pattern id="liquid" width="172" height="72" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/> <circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/> <circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/> <circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
@ -415,37 +415,6 @@
<stop stop-color="#727171" offset=".89138"/> <stop stop-color="#727171" offset=".89138"/>
<stop stop-color="#727171" offset="1"/> <stop stop-color="#727171" offset="1"/>
</linearGradient> </linearGradient>
<pattern id="vertical-liquid" width="172" height="72" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
<circle transform="rotate(-90)" cx="-58" cy="34" r="8" fill="url(#paint31_linear_1182_32781-4)"/>
<circle transform="rotate(-90)" cx="-58" cy="155" r="8" fill="url(#paint32_linear_1182_32781-3)"/>
<circle transform="rotate(-90)" cx="-26" cy="33" r="5" fill="url(#paint33_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-26" cy="162" r="5" fill="url(#paint34_linear_1182_32781-4)"/>
<circle transform="rotate(-90)" cx="-37" cy="5" r="5" fill="url(#paint35_linear_1182_32781-6)"/>
<circle transform="rotate(-90)" cx="-8" cy="94" r="4" fill="url(#paint36_linear_1182_32781-9)"/>
<circle transform="rotate(-90)" cx="-60" cy="72" r="4" fill="url(#paint37_linear_1182_32781-4)"/>
<circle transform="rotate(-90)" cx="-37" cy="112" r="5" fill="url(#paint38_linear_1182_32781-2)"/>
<circle transform="rotate(-90)" cx="-39" cy="59" r="5" fill="url(#paint39_linear_1182_32781-2)"/>
<circle transform="rotate(-90)" cx="-62" cy="115" r="5" fill="url(#paint40_linear_1182_32781-6)"/>
<circle transform="rotate(-90)" cx="-42" cy="139" r="5" fill="url(#paint41_linear_1182_32781-4)"/>
<circle transform="rotate(-90)" cx="-21" cy="76" r="5" fill="url(#paint42_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-50.5" cy="126.5" r="2.5" fill="url(#paint43_linear_1182_32781-2)"/>
<circle transform="rotate(-90)" cx="-46.5" cy="169.5" r="2.5" fill="url(#paint44_linear_1182_32781-8)"/>
<circle transform="rotate(-90)" cx="-9.5" cy="57.5" r="2.5" fill="url(#paint45_linear_1182_32781-8)"/>
<circle transform="rotate(-90)" cx="-35.5" cy="96.5" r="2.5" fill="url(#paint46_linear_1182_32781-9)"/>
<circle transform="rotate(-90)" cx="-23.5" cy="91.5" r="2.5" fill="url(#paint47_linear_1182_32781-2)"/>
<circle transform="rotate(-90)" cx="-40.5" cy="22.5" r="2.5" fill="url(#paint48_linear_1182_32781-88)"/>
<circle transform="rotate(-90)" cx="-23.5" cy="124.5" r="2.5" fill="url(#paint49_linear_1182_32781-8)"/>
<circle transform="rotate(-90)" cx="-47.5" cy="86.5" r="2.5" fill="url(#paint50_linear_1182_32781-6)"/>
<circle transform="rotate(-90)" cx="-21.5" cy="51.5" r="2.5" fill="url(#paint51_linear_1182_32781-8)"/>
<circle transform="rotate(-90)" cx="-51.5" cy="48.5" r="2.5" fill="url(#paint52_linear_1182_32781-3)"/>
<circle transform="rotate(-90)" cx="-64" cy="14" r="4" fill="url(#paint53_linear_1182_32781-83)"/>
<circle transform="rotate(-90)" cx="-64" cy="135" r="4" fill="url(#paint54_linear_1182_32781-33)"/>
<circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/>
<path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/>
</pattern>
<clipPath id="clip0_1245_66459"> <clipPath id="clip0_1245_66459">
<rect width="200" height="200" fill="#fff"/> <rect width="200" height="200" fill="#fff"/>
</clipPath> </clipPath>
@ -530,7 +499,12 @@
<clipPath id="clipPath4208"> <clipPath id="clipPath4208">
<rect width="200" height="200" fill="#fff"/> <rect width="200" height="200" fill="#fff"/>
</clipPath> </clipPath>
<pattern id="center-liquid" patternTransform="scale(1.17) rotate(-45)" patternUnits="userSpaceOnUse" xlink:href="#horizontal-liquid"/> <pattern id="base-horizontal-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="horizontal-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect x="-172" width="688" height="72" fill="url(#base-horizontal-liquid)" stroke-width="0"/></pattern>
<pattern id="base-vertical-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="vertical-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect x="-172" width="688" height="72" fill="url(#base-vertical-liquid)" stroke-width="0"/></pattern>
<pattern id="base-center-liquid" width="172" height="72" patternTransform="translate(15)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="center-liquid" width="172" height="72" patternTransform="scale(1.17) rotate(-45)" patternUnits="userSpaceOnUse"><rect x="-172" width="688" height="72" fill="url(#base-center-liquid)" stroke-width="0"/></pattern>
</defs><rect x="14" y="64" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect transform="rotate(90)" x="14" y="-136" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect x="14" y="64" width="50" height="72" fill="url(#horizontal-liquid)" stroke-width="0" style="display: none;" tb:tag="horizontal-fluid"/><rect transform="rotate(-90)" x="-64" y="64" width="50" height="72" fill="url(#vertical-liquid)" stroke-width="0" style="display: none;" tb:tag="vertical-fluid"/><path d="m64 64v-0.1h72c-0.0899 0.87013-0.23101 2.2585-0.44644 3.7063-0.48479 3.257-1.345 7.819-2.8448 13.02-3.0056 10.422-8.5473 23.299-18.676 33.421-10.129 10.122-23.014 15.66-33.444 18.663-5.2052 1.4993-9.7699 2.3586-13.03 2.843-1.4481 0.21507-2.789 0.35602-3.6593 0.44685v-72z" fill="#1ec1f4" stroke="#000" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><g transform="matrix(.99929 0 0 .99919 .089337 .10125)" style="display: none;" tb:tag="center-fluid"> </defs><rect x="14" y="64" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect transform="rotate(90)" x="14" y="-136" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect x="14" y="64" width="50" height="72" fill="url(#horizontal-liquid)" stroke-width="0" style="display: none;" tb:tag="horizontal-fluid"/><rect transform="rotate(-90)" x="-64" y="64" width="50" height="72" fill="url(#vertical-liquid)" stroke-width="0" style="display: none;" tb:tag="vertical-fluid"/><path d="m64 64v-0.1h72c-0.0899 0.87013-0.23101 2.2585-0.44644 3.7063-0.48479 3.257-1.345 7.819-2.8448 13.02-3.0056 10.422-8.5473 23.299-18.676 33.421-10.129 10.122-23.014 15.66-33.444 18.663-5.2052 1.4993-9.7699 2.3586-13.03 2.843-1.4481 0.21507-2.789 0.35602-3.6593 0.44685v-72z" fill="#1ec1f4" stroke="#000" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><g transform="matrix(.99929 0 0 .99919 .089337 .10125)" style="display: none;" tb:tag="center-fluid">
<path d="m63.956 63.95 72.051-0.10008c-0.0901 0.87205-0.23133 2.1632-0.44707 3.6142-0.48547 3.2643-1.3469 7.8362-2.8488 13.048-3.0098 10.446-8.5594 23.351-18.702 33.495-10.143 10.145-23.046 15.695-33.491 18.704-5.2125 1.5026-9.7836 2.3638-13.048 2.8493-1.4501 0.21555-2.7428 0.3568-3.6144 0.44784z" fill="url(#center-liquid)" stroke="#000" stroke-width="0" style="" tb:tag="center-fluid-background"/> <path d="m63.956 63.95 72.051-0.10008c-0.0901 0.87205-0.23133 2.1632-0.44707 3.6142-0.48547 3.2643-1.3469 7.8362-2.8488 13.048-3.0098 10.446-8.5594 23.351-18.702 33.495-10.143 10.145-23.046 15.695-33.491 18.704-5.2125 1.5026-9.7836 2.3638-13.048 2.8493-1.4501 0.21555-2.7428 0.3568-3.6144 0.44784z" fill="url(#center-liquid)" stroke="#000" stroke-width="0" style="" tb:tag="center-fluid-background"/>
<path d="m132.05 63.65s-0.0216 31.924-17.622 50.777c-17.142 17.939-50.672 17.628-50.672 17.628" fill="none" stroke="#727171" stroke-width="7.9" style=""/> <path d="m132.05 63.65s-0.0216 31.924-17.622 50.777c-17.142 17.939-50.672 17.628-50.672 17.628" fill="none" stroke="#727171" stroke-width="7.9" style=""/>

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 49 KiB

11
application/src/main/data/json/system/scada_symbols/long-horizontal-pipe.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="200" fill="none" version="1.1" viewBox="0 0 400 200"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="200" fill="none" version="1.1" viewBox="0 0 400 200"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Long horizontal pipe", "title": "Long horizontal pipe",
"description": "Long horizontal pipe with fluid and leak visualizations.", "description": "Long horizontal pipe with fluid and leak visualizations.",
"searchTags": [ "searchTags": [
@ -11,7 +10,7 @@
"tags": [ "tags": [
{ {
"tag": "fluid", "tag": "fluid",
"stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill');\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(ctx.values.flowAnimationSpeed);\n }\n} else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}", "stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill').first();\n\nvar fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(ctx.values.flowAnimationSpeed);\n }\n }\n} else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}",
"actions": null "actions": null
}, },
{ {
@ -240,7 +239,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><path d="m14 64h372v72h-372v-72z" fill="#fff" tb:tag="pipe-background"/><path d="m14 64h372v72h-372v-72z" fill="url(#paint0_linear_1239_51060)"/><g stroke-width="3"> }]]></tb:metadata>
<path d="m14 64h372v72h-372v-72z" fill="#fff" tb:tag="pipe-background"/><path d="m14 64h372v72h-372v-72z" fill="url(#paint0_linear_1239_51060)"/><g stroke-width="3">
<path d="m15.5 65.5h369v69h-369v-69z" stroke="#000" stroke-opacity=".12"/> <path d="m15.5 65.5h369v69h-369v-69z" stroke="#000" stroke-opacity=".12"/>
<rect x="387.5" y="51.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171"/> <rect x="387.5" y="51.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171"/>
<rect x="1.5" y="51.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171"/> <rect x="1.5" y="51.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171"/>
@ -254,7 +254,7 @@
<stop stop-color="#727171" stop-opacity=".35" offset=".71855"/> <stop stop-color="#727171" stop-opacity=".35" offset=".71855"/>
<stop stop-color="#727171" offset="1"/> <stop stop-color="#727171" offset="1"/>
</linearGradient> </linearGradient>
<pattern id="liquid" width="172" height="72" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse"> <pattern id="baseLiquid" width="172" height="72" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/> <circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/> <circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/> <circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
@ -285,6 +285,7 @@
<circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/> <circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/>
<path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/> <path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/>
</pattern> </pattern>
<pattern id="liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#baseLiquid)"/></pattern>
<linearGradient id="paint28_linear_1182_32781-5" x1="19.316" x2="21" y1="8.2632" y2="23" gradientTransform="translate(-36,6)" gradientUnits="userSpaceOnUse"> <linearGradient id="paint28_linear_1182_32781-5" x1="19.316" x2="21" y1="8.2632" y2="23" gradientTransform="translate(-36,6)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/> <stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".15" offset="1"/> <stop stop-opacity=".15" offset="1"/>

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

11
application/src/main/data/json/system/scada_symbols/long-vertical-pipe.svg

@ -1,5 +1,4 @@
<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": "Long vertical pipe", "title": "Long vertical pipe",
"description": "Long vertical pipe with fluid and leak visualizations.", "description": "Long vertical pipe with fluid and leak visualizations.",
"searchTags": [ "searchTags": [
@ -11,7 +10,7 @@
"tags": [ "tags": [
{ {
"tag": "fluid", "tag": "fluid",
"stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill');\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(ctx.values.flowAnimationSpeed);\n }\n} else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}", "stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill').first();\n\nvar fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(ctx.values.flowAnimationSpeed);\n }\n }\n} else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}",
"actions": null "actions": null
}, },
{ {
@ -240,7 +239,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><path d="m64 386v-372h72v372h-72z" fill="#fff" tb:tag="pipe-background"/><path d="m64 386v-372h72v372h-72z" fill="url(#paint0_linear_1242_51682)"/><g stroke-width="3"> }]]></tb:metadata>
<path d="m64 386v-372h72v372h-72z" fill="#fff" tb:tag="pipe-background"/><path d="m64 386v-372h72v372h-72z" fill="url(#paint0_linear_1242_51682)"/><g stroke-width="3">
<path d="m65.5 384.5v-369h69v369h-69z" stroke="#000" stroke-opacity=".12"/> <path d="m65.5 384.5v-369h69v369h-69z" stroke="#000" stroke-opacity=".12"/>
<rect transform="rotate(-90 51.5 398.5)" x="51.5" y="398.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171"/> <rect transform="rotate(-90 51.5 398.5)" x="51.5" y="398.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171"/>
<rect transform="rotate(-90 51.5 12.5)" x="51.5" y="12.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171"/> <rect transform="rotate(-90 51.5 12.5)" x="51.5" y="12.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171"/>
@ -254,7 +254,7 @@
<stop stop-color="#727171" stop-opacity=".35" offset=".71855"/> <stop stop-color="#727171" stop-opacity=".35" offset=".71855"/>
<stop stop-color="#727171" offset="1"/> <stop stop-color="#727171" offset="1"/>
</linearGradient> </linearGradient>
<pattern id="liquid" width="172" height="72" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse"> <pattern id="baseLiquid" width="172" height="72" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/> <circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/> <circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/> <circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
@ -285,6 +285,7 @@
<circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/> <circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/>
<path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/> <path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/>
</pattern> </pattern>
<pattern id="liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#baseLiquid)"/></pattern>
<linearGradient id="paint28_linear_1182_32781-5" x1="19.316" x2="21" y1="8.2632" y2="23" gradientTransform="translate(-36,6)" gradientUnits="userSpaceOnUse"> <linearGradient id="paint28_linear_1182_32781-5" x1="19.316" x2="21" y1="8.2632" y2="23" gradientTransform="translate(-36,6)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/> <stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".15" offset="1"/> <stop stop-opacity=".15" offset="1"/>

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 39 KiB

15
application/src/main/data/json/system/scada_symbols/pool.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="2400" height="800" fill="none" version="1.1" viewBox="0 0 2400 800"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="2400" height="800" fill="none" version="1.1" viewBox="0 0 2400 800"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Pool", "title": "Pool",
"description": "Pool with current volume value and level visualizations.", "description": "Pool with current volume value and level visualizations.",
"searchTags": [ "searchTags": [
@ -24,12 +23,12 @@
}, },
{ {
"tag": "fluid", "tag": "fluid",
"stateRenderFunction": "if (!ctx.properties.transparent) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*740; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n", "stateRenderFunction": "if (!ctx.properties.transparent) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*740; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n",
"actions": null "actions": null
}, },
{ {
"tag": "fluid-background", "tag": "fluid-background",
"stateRenderFunction": "if (!ctx.properties.transparent) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*740; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n", "stateRenderFunction": "if (!ctx.properties.transparent) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*740; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n",
"actions": null "actions": null
}, },
{ {
@ -279,7 +278,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><path d="m0 12c0-6.6274 5.3726-12 12-12h2376c6.63 0 12 5.3726 12 12v776c0 6.627-5.37 12-12 12h-2376c-6.6274 0-12-5.373-12-12v-776z" fill="#E5E5E5" tb:tag="background"/><path d="m0 12c0-6.6274 5.3726-12 12-12h2376c6.63 0 12 5.3726 12 12v776c0 6.627-5.37 12-12 12h-2376c-6.6274 0-12-5.373-12-12v-776z" fill="url(#paint0_linear_2028_445065)"/><path d="m1.5 12c0-5.799 4.701-10.5 10.5-10.5h2376c5.8 0 10.5 4.701 10.5 10.5v776c0 5.799-4.7 10.5-10.5 10.5h-2376c-5.799 0-10.5-4.701-10.5-10.5v-776z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m0 12c0-6.6274 5.3726-12 12-12h2376c6.63 0 12 5.3726 12 12v776c0 6.628-5.37 12-12 12h-2376c-6.6274 0-12-5.372-12-12v-776z" fill="#4A4848" fill-opacity=".5"/><mask id="mask0_2028_445065" x="16" y="56" width="2368" height="728" style="mask-type:alpha" maskUnits="userSpaceOnUse"> }]]></tb:metadata>
<path d="m0 12c0-6.6274 5.3726-12 12-12h2376c6.63 0 12 5.3726 12 12v776c0 6.627-5.37 12-12 12h-2376c-6.6274 0-12-5.373-12-12v-776z" fill="#E5E5E5" tb:tag="background"/><path d="m0 12c0-6.6274 5.3726-12 12-12h2376c6.63 0 12 5.3726 12 12v776c0 6.627-5.37 12-12 12h-2376c-6.6274 0-12-5.373-12-12v-776z" fill="url(#paint0_linear_2028_445065)"/><path d="m1.5 12c0-5.799 4.701-10.5 10.5-10.5h2376c5.8 0 10.5 4.701 10.5 10.5v776c0 5.799-4.7 10.5-10.5 10.5h-2376c-5.799 0-10.5-4.701-10.5-10.5v-776z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m0 12c0-6.6274 5.3726-12 12-12h2376c6.63 0 12 5.3726 12 12v776c0 6.628-5.37 12-12 12h-2376c-6.6274 0-12-5.372-12-12v-776z" fill="#4A4848" fill-opacity=".5"/><mask id="mask0_2028_445065" x="16" y="56" width="2368" height="728" style="mask-type:alpha" maskUnits="userSpaceOnUse">
<path d="m16 56h359 355 444.5 394 230 238.5 343.13c2.21 0 3.87 1.7909 3.87 4v712c0 6.627-5.37 12-12 12h-2344c-6.6274 0-12-5.373-12-12v-716z" fill="#D9D9D9"/> <path d="m16 56h359 355 444.5 394 230 238.5 343.13c2.21 0 3.87 1.7909 3.87 4v712c0 6.627-5.37 12-12 12h-2344c-6.6274 0-12-5.373-12-12v-716z" fill="#D9D9D9"/>
</mask><g mask="url(#mask0_2028_445065)"> </mask><g mask="url(#mask0_2028_445065)">
<rect transform="scale(1,-1)" x="16" y="-790" width="2368" height="600" fill="#1ec1f4" fill-opacity=".5" tb:tag="fluid-background"/> <rect transform="scale(1,-1)" x="16" y="-790" width="2368" height="600" fill="#1ec1f4" fill-opacity=".5" tb:tag="fluid-background"/>
@ -291,7 +291,7 @@
</g><g filter="url(#filter0_ii_2028_445065)" tb:tag="value-box"> </g><g filter="url(#filter0_ii_2028_445065)" tb:tag="value-box">
<path d="m1076 372.19c0-8.9433 6.8853-16.194 15.378-16.194h276.79c8.4922 0 15.377 7.251 15.377 16.194v75.573c0 8.9433-6.8852 16.194-15.377 16.194h-276.79c-8.4922 0-15.378-7.251-15.378-16.194z" fill="#f3f3f3" tb:tag="value-box-background"/> <path d="m1076 372.19c0-8.9433 6.8853-16.194 15.378-16.194h276.79c8.4922 0 15.377 7.251 15.377 16.194v75.573c0 8.9433-6.8852 16.194-15.377 16.194h-276.79c-8.4922 0-15.378-7.251-15.378-16.194z" fill="#f3f3f3" tb:tag="value-box-background"/>
<path d="m1078 372.16c0-7.8336 6.0222-14.184 13.451-14.184h276.7c7.4286 0 13.451 6.3503 13.451 14.184v75.647c0 7.8336-6.0221 14.184-13.451 14.184h-276.7c-7.4285 0-13.451-6.3503-13.451-14.184z" stroke="#727171" stroke-width="3.9464"/> <path d="m1078 372.16c0-7.8336 6.0222-14.184 13.451-14.184h276.7c7.4286 0 13.451 6.3503 13.451 14.184v75.647c0 7.8336-6.0221 14.184-13.451 14.184h-276.7c-7.4285 0-13.451-6.3503-13.451-14.184z" stroke="#727171" stroke-width="3.9464"/>
<text x="1235.9021" y="410.52652" dominant-baseline="middle" fill="#727171" font-family="Roboto" font-size="32px" font-weight="500" text-anchor="middle" tb:tag="value-text" xml:space="preserve"><tspan>1660 gal</tspan></text> <text x="1235.9021" y="410.52652" fill="#727171" font-family="Roboto" font-size="32px" font-weight="500" tb:tag="value-text" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">1660 gal</tspan></text>
</g><path d="m2388 0c6.63 0 12 5.3726 12 12v15c0 6.6274-5.37 12-12 12h-2376c-6.6274 0-12-5.3726-12-12v-15c0-6.6274 5.3726-12 12-12h2376z" fill="#647484"/><path d="m2388 0c6.63 0 12 5.3726 12 12v15c0 6.6274-5.37 12-12 12h-2376c-6.6274 0-12-5.3726-12-12v-15c0-6.6274 5.3726-12 12-12h2376z" fill="url(#paint1846_linear_2028_445065)"/><path d="m2388 2c5.52 0 10 4.4772 10 10v15c0 5.5229-4.48 10-10 10h-2376c-5.523 0-10-4.4772-10-10v-15c0-5.5228 4.477-10 10-10h2376z" stroke="#000" stroke-opacity=".12" stroke-width="4"/><path d="m807.17 0s-807.17 0-807.17 134v656.72c0 5.3024 21.49 9.2807 48 9.2807h2304c26.508 0 48-3.9784 48-9.2807v-656.72c0-134-792.84-134-792.84-134h-407.16zm804.84 162.4c-15.464 0-28.001 2.5072-28.001 5.6v600.8c0 3.0928 12.536 5.6 28.001 5.6h176c15.464 0 28.001-2.5072 28.001-5.6v-600.8c0-3.0928-12.536-5.6-28.001-5.6z" fill="#000" fill-opacity="0" style="stroke-width:1.2649" tb:tag="clickArea"/><defs> </g><path d="m2388 0c6.63 0 12 5.3726 12 12v15c0 6.6274-5.37 12-12 12h-2376c-6.6274 0-12-5.3726-12-12v-15c0-6.6274 5.3726-12 12-12h2376z" fill="#647484"/><path d="m2388 0c6.63 0 12 5.3726 12 12v15c0 6.6274-5.37 12-12 12h-2376c-6.6274 0-12-5.3726-12-12v-15c0-6.6274 5.3726-12 12-12h2376z" fill="url(#paint1846_linear_2028_445065)"/><path d="m2388 2c5.52 0 10 4.4772 10 10v15c0 5.5229-4.48 10-10 10h-2376c-5.523 0-10-4.4772-10-10v-15c0-5.5228 4.477-10 10-10h2376z" stroke="#000" stroke-opacity=".12" stroke-width="4"/><path d="m807.17 0s-807.17 0-807.17 134v656.72c0 5.3024 21.49 9.2807 48 9.2807h2304c26.508 0 48-3.9784 48-9.2807v-656.72c0-134-792.84-134-792.84-134h-407.16zm804.84 162.4c-15.464 0-28.001 2.5072-28.001 5.6v600.8c0 3.0928 12.536 5.6 28.001 5.6h176c15.464 0 28.001-2.5072 28.001-5.6v-600.8c0-3.0928-12.536-5.6-28.001-5.6z" fill="#000" fill-opacity="0" style="stroke-width:1.2649" tb:tag="clickArea"/><defs>
<filter id="filter0_ii_2028_445065" x="1076" y="356" width="308" height="108" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"> <filter id="filter0_ii_2028_445065" x="1076" y="356" width="308" height="108" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
<feFlood flood-opacity="0" result="BackgroundImageFix"/> <feFlood flood-opacity="0" result="BackgroundImageFix"/>
@ -324,7 +324,7 @@
<stop stop-color="#727171" stop-opacity=".7" offset="1"/> <stop stop-color="#727171" stop-opacity=".7" offset="1"/>
</linearGradient> </linearGradient>
<linearGradient id="linearGradient13024" x1="2400" x2="6.4263" y1="402.68" y2="276.76" gradientUnits="userSpaceOnUse" xlink:href="#paint0_linear_2028_445065"/> <linearGradient id="linearGradient13024" x1="2400" x2="6.4263" y1="402.68" y2="276.76" gradientUnits="userSpaceOnUse" xlink:href="#paint0_linear_2028_445065"/>
<pattern id="liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid"> <pattern id="base-liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/> <circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/>
<circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/> <circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/>
<circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/> <circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/>
@ -472,6 +472,7 @@
<circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/> <circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/>
<path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/> <path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/>
</pattern> </pattern>
<pattern id="liquid" width="584" height="201" patternUnits="userSpaceOnUse"><rect width="584" height="201" x="0" y="0" stroke-width="0" fill="url(#base-liquid)"/></pattern>
<linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse"> <linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/> <stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".05" offset="1"/> <stop stop-opacity=".05" offset="1"/>

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 73 KiB

6
application/src/main/data/json/system/scada_symbols/right-analog-water-level-meter.svg

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

15
application/src/main/data/json/system/scada_symbols/right-flow-meter.svg

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 74 KiB

12
application/src/main/data/json/system/scada_symbols/right-heat-pump.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="800" height="600" fill="none" version="1.1" viewBox="0 0 800 600"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="800" height="600" fill="none" version="1.1" viewBox="0 0 800 600"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Right heat pump", "title": "Right heat pump",
"description": "Right heat pump with configurable connectors, running animation and various states.", "description": "Right heat pump with configurable connectors, running animation and various states.",
"searchTags": [ "searchTags": [
@ -26,12 +25,12 @@
}, },
{ {
"tag": "fan", "tag": "fan",
"stateRenderFunction": "var running = ctx.values.running;\nvar t = element.timeline();\nif (running) {\n if (!t.active()) {\n if (t.time()) {\n t.play();\n } else {\n ctx.api.animate(element, 2000).ease('-').rotate(360).loop();\n t = element.timeline();\n }\n }\n var speed = ctx.values.rotationAnimationSpeed;\n t.speed(speed);\n} else {\n t.pause();\n}\n", "stateRenderFunction": "var running = ctx.values.running;\nvar speed = ctx.values.rotationAnimationSpeed;\nvar fanRotate = ctx.api.cssAnimation(element);\nif (running) {\n if (!fanRotate) {\n fanRotate = ctx.api.cssAnimate(element, 2000)\n .rotate(360).loop().speed(speed);\n } else {\n fanRotate.speed(speed).play();\n }\n} else {\n if (fanRotate) {\n fanRotate.pause();\n }\n}",
"actions": null "actions": null
}, },
{ {
"tag": "fan-blade", "tag": "fan-blade",
"stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}", "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (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}",
"actions": null "actions": null
}, },
{ {
@ -673,14 +672,15 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><rect x="254" y="571" width="300" height="29" rx="7" fill="#fff"/><rect x="254" y="571" width="300" height="29" rx="7" fill="url(#paint0_linear_1826_356190)"/><rect x="255.5" y="572.5" width="297" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m307 545h194l32 26h-258l32-26z" fill="#fff"/><path d="m307 545h194l32 26h-258l32-26z" fill="url(#paint1_linear_1826_356190)"/><path d="m279.22 569.5 28.308-23h192.93l28.308 23h-249.55z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="matrix(-1 0 0 1 767 0)" width="763" height="545" rx="12" fill="#E5E5E5"/><rect transform="matrix(-1 0 0 1 767 0)" width="763" height="545" rx="12" fill="url(#paint2_linear_1826_356190)"/><rect transform="matrix(-1 0 0 1 764 0)" x="-1.5" y="1.5" width="760" height="542" rx="10.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m767 12c0-6.6274-5.373-12-12-12h-739c-6.6274 0-12 5.3726-12 12v521c0 6.627 5.3726 12 12 12h739c6.627 0 12-5.373 12-12v-521z" fill="#4A4848" fill-opacity=".5"/><rect transform="matrix(-1 0 0 1 801 50)" x="-1.5" y="1.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171" stroke-width="3"/><path d="m790 64h-23v72h23v-72z" fill="#fff" tb:tag="pipe-background"/><path d="m790 64h-23v72h23v-72z" fill="url(#paint3_linear_1826_356190)"/><path d="m788.5 65.5h-20v69h20v-69z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="matrix(-1 0 0 1 801 250)" x="-1.5" y="1.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171" stroke-width="3"/><path d="m790 264h-23v72h23v-72z" fill="#fff" tb:tag="pipe-background"/><path d="m790 264h-23v72h23v-72z" fill="url(#paint4_linear_1826_356190)"/><path d="m788.5 265.5h-20v69h20v-69z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="4" width="763" height="545" rx="12" fill="#E5E5E5" tb:tag="background"/><rect x="4" width="763" height="545" rx="12" fill="url(#paint5_linear_1826_356190)"/><rect x="5.5" y="1.5" width="760" height="542" rx="10.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><g filter="url(#filter0_d_1826_356190)"> }]]></tb:metadata>
<rect x="254" y="571" width="300" height="29" rx="7" fill="#fff"/><rect x="254" y="571" width="300" height="29" rx="7" fill="url(#paint0_linear_1826_356190)"/><rect x="255.5" y="572.5" width="297" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m307 545h194l32 26h-258l32-26z" fill="#fff"/><path d="m307 545h194l32 26h-258l32-26z" fill="url(#paint1_linear_1826_356190)"/><path d="m279.22 569.5 28.308-23h192.93l28.308 23h-249.55z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="matrix(-1 0 0 1 767 0)" width="763" height="545" rx="12" fill="#E5E5E5"/><rect transform="matrix(-1 0 0 1 767 0)" width="763" height="545" rx="12" fill="url(#paint2_linear_1826_356190)"/><rect transform="matrix(-1 0 0 1 764 0)" x="-1.5" y="1.5" width="760" height="542" rx="10.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m767 12c0-6.6274-5.373-12-12-12h-739c-6.6274 0-12 5.3726-12 12v521c0 6.627 5.3726 12 12 12h739c6.627 0 12-5.373 12-12v-521z" fill="#4A4848" fill-opacity=".5"/><rect transform="matrix(-1 0 0 1 801 50)" x="-1.5" y="1.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171" stroke-width="3"/><path d="m790 64h-23v72h23v-72z" fill="#fff" tb:tag="pipe-background"/><path d="m790 64h-23v72h23v-72z" fill="url(#paint3_linear_1826_356190)"/><path d="m788.5 65.5h-20v69h20v-69z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="matrix(-1 0 0 1 801 250)" x="-1.5" y="1.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171" stroke-width="3"/><path d="m790 264h-23v72h23v-72z" fill="#fff" tb:tag="pipe-background"/><path d="m790 264h-23v72h23v-72z" fill="url(#paint4_linear_1826_356190)"/><path d="m788.5 265.5h-20v69h20v-69z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="4" width="763" height="545" rx="12" fill="#E5E5E5" tb:tag="background"/><rect x="4" width="763" height="545" rx="12" fill="url(#paint5_linear_1826_356190)"/><rect x="5.5" y="1.5" width="760" height="542" rx="10.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><g filter="url(#filter0_d_1826_356190)">
<path d="m767 12c0-6.6274-5.373-12-12-12h-739c-6.6274 0-12 5.3726-12 12v521c0 6.627 5.3726 12 12 12h161.7c0.532 0 1.002-0.027 1.529-0.1 13.196-1.836 202.34-29.137 319.77-101.9 151.45-93.843 252.83-258.48 266.44-281.34 1.072-1.801 1.565-3.765 1.565-5.86v-143.8z" fill="#E5E5E5" tb:tag="background"/> <path d="m767 12c0-6.6274-5.373-12-12-12h-739c-6.6274 0-12 5.3726-12 12v521c0 6.627 5.3726 12 12 12h161.7c0.532 0 1.002-0.027 1.529-0.1 13.196-1.836 202.34-29.137 319.77-101.9 151.45-93.843 252.83-258.48 266.44-281.34 1.072-1.801 1.565-3.765 1.565-5.86v-143.8z" fill="#E5E5E5" tb:tag="background"/>
<path d="m767 12c0-6.6274-5.373-12-12-12h-739c-6.6274 0-12 5.3726-12 12v521c0 6.627 5.3726 12 12 12h161.7c0.532 0 1.002-0.027 1.529-0.1 13.196-1.836 202.34-29.137 319.77-101.9 151.45-93.843 252.83-258.48 266.44-281.34 1.072-1.801 1.565-3.765 1.565-5.86v-143.8z" fill="url(#paint6_linear_1826_356190)"/> <path d="m767 12c0-6.6274-5.373-12-12-12h-739c-6.6274 0-12 5.3726-12 12v521c0 6.627 5.3726 12 12 12h161.7c0.532 0 1.002-0.027 1.529-0.1 13.196-1.836 202.34-29.137 319.77-101.9 151.45-93.843 252.83-258.48 266.44-281.34 1.072-1.801 1.565-3.765 1.565-5.86v-143.8z" fill="url(#paint6_linear_1826_356190)"/>
<path d="m765.5 12c0-5.799-4.701-10.5-10.5-10.5h-739c-5.799 0-10.5 4.701-10.5 10.5v521c0 5.799 4.701 10.5 10.5 10.5h161.7c0.475 0 0.875-0.024 1.322-0.086 6.567-0.913 57.094-8.188 120.64-24.32 63.568-16.138 140.03-41.11 198.55-77.369 151.12-93.639 252.34-258 265.94-280.83 0.922-1.549 1.354-3.244 1.354-5.093v-143.8z" stroke="#000" stroke-opacity=".12" stroke-width="3"/> <path d="m765.5 12c0-5.799-4.701-10.5-10.5-10.5h-739c-5.799 0-10.5 4.701-10.5 10.5v521c0 5.799 4.701 10.5 10.5 10.5h161.7c0.475 0 0.875-0.024 1.322-0.086 6.567-0.913 57.094-8.188 120.64-24.32 63.568-16.138 140.03-41.11 198.55-77.369 151.12-93.639 252.34-258 265.94-280.83 0.922-1.549 1.354-3.244 1.354-5.093v-143.8z" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
</g><g filter="url(#filter2_ii_1826_356190)"> </g><g filter="url(#filter2_ii_1826_356190)">
<path d="m499 82c0-6.6274 5.373-12 12-12h157c6.627 0 12 5.3726 12 12v56c0 6.627-5.373 12-12 12h-157c-6.627 0-12-5.373-12-12v-56z" fill="#FFFEFE" tb:tag="value-box-background"/> <path d="m499 82c0-6.6274 5.373-12 12-12h157c6.627 0 12 5.3726 12 12v56c0 6.627-5.373 12-12 12h-157c-6.627 0-12-5.373-12-12v-56z" fill="#FFFEFE" tb:tag="value-box-background"/>
<path d="m500 82c0-6.0751 4.925-11 11-11h157c6.075 0 11 4.9249 11 11v56c0 6.075-4.925 11-11 11h-157c-6.075 0-11-4.925-11-11v-56z" stroke="#fff" stroke-width="2"/> <path d="m500 82c0-6.0751 4.925-11 11-11h157c6.075 0 11 4.9249 11 11v56c0 6.075-4.925 11-11 11h-157c-6.075 0-11-4.925-11-11v-56z" stroke="#fff" stroke-width="2"/>
<text x="589.51953" y="113.75" dominant-baseline="middle" fill="#000000" font-family="Roboto" font-size="40px" font-weight="500" text-anchor="middle" tb:tag="value-text" xml:space="preserve"><tspan>27</tspan></text> <text x="589.51953" y="113.75" fill="#000000" font-family="Roboto" font-size="40px" font-weight="500" tb:tag="value-text" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">27</tspan></text>
</g><circle cx="274" cy="278.2" r="180" fill="#B6B4B4"/><circle cx="274" cy="278.2" r="180" fill="url(#paint7_radial_1826_356190)"/><circle cx="274" cy="278.2" r="178.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><g transform="translate(93.999 97.999)" tb:tag="fan"> </g><circle cx="274" cy="278.2" r="180" fill="#B6B4B4"/><circle cx="274" cy="278.2" r="180" fill="url(#paint7_radial_1826_356190)"/><circle cx="274" cy="278.2" r="178.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><g transform="translate(93.999 97.999)" tb:tag="fan">
<circle cx="180" cy="180.2" r="180" fill="#b6b4b4"/> <circle cx="180" cy="180.2" r="180" fill="#b6b4b4"/>
<circle cx="180" cy="180.2" r="178.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/> <circle cx="180" cy="180.2" r="178.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/>

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

2
application/src/main/data/json/system/scada_symbols/right-motor-pump.svg

@ -10,7 +10,7 @@
"tags": [ "tags": [
{ {
"tag": "background", "tag": "background",
"stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}", "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (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}",
"actions": null "actions": null
}, },
{ {

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

17
application/src/main/data/json/system/scada_symbols/right-tee-pipe.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Right tee pipe", "title": "Right tee pipe",
"description": "Right tee pipe with configurable right/top/bottom fluid and leak visualizations.", "description": "Right tee pipe with configurable right/top/bottom fluid and leak visualizations.",
"searchTags": [ "searchTags": [
@ -8,7 +7,7 @@
], ],
"widgetSizeX": 1, "widgetSizeX": 1,
"widgetSizeY": 1, "widgetSizeY": 1,
"stateRenderFunction": "var rightLiquidPattern = prepareLiquidPattern('right-fluid');\nvar topLiquidPattern = prepareLiquidPattern('top-fluid');\nvar bottomLiquidPattern = prepareLiquidPattern('bottom-fluid');\n\nupdateLiquidPatternAnimation(rightLiquidPattern, 'right');\nupdateLiquidPatternAnimation(topLiquidPattern, 'top');\nupdateLiquidPatternAnimation(bottomLiquidPattern, 'bottom');\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill');\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, prefix) {\n if (liquidPattern) {\n var fluid = ctx.values[prefix + 'Fluid'] && !ctx.values.leak;\n var flow = ctx.values[prefix + 'Flow'];\n var flowDirection = ctx.values[prefix + 'FlowDirection'];\n var flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(flowAnimationSpeed);\n }\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n}\n\n", "stateRenderFunction": "var rightLiquidPattern = prepareLiquidPattern('right-fluid');\nvar topLiquidPattern = prepareLiquidPattern('top-fluid');\nvar bottomLiquidPattern = prepareLiquidPattern('bottom-fluid');\n\nupdateLiquidPatternAnimation(rightLiquidPattern, 'right');\nupdateLiquidPatternAnimation(topLiquidPattern, 'top');\nupdateLiquidPatternAnimation(bottomLiquidPattern, 'bottom');\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill').first();\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, prefix) {\n if (liquidPattern) {\n var fluid = ctx.values[prefix + 'Fluid'] && !ctx.values.leak;\n var flow = ctx.values[prefix + 'Flow'];\n var flowDirection = ctx.values[prefix + 'FlowDirection'];\n var flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n var fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(flowAnimationSpeed);\n }\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}\n",
"tags": [ "tags": [
{ {
"tag": "bottom-fluid", "tag": "bottom-fluid",
@ -576,7 +575,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><g clip-path="url(#clip0_1245_66636)"> }]]></tb:metadata>
<g clip-path="url(#clip0_1245_66636)">
<path d="m64 186v-172h72v172z" fill="#fff" tb:tag="pipe-background"/> <path d="m64 186v-172h72v172z" fill="#fff" tb:tag="pipe-background"/>
<path d="m64 186v-172h72v172z" fill="url(#paint0_linear_1245_66636)" style="fill:url(#paint0_linear_1245_66442)"/> <path d="m64 186v-172h72v172z" fill="url(#paint0_linear_1245_66636)" style="fill:url(#paint0_linear_1245_66442)"/>
<path d="m65.5 184.5v-169h69v169z" stroke="#000" stroke-opacity=".12" stroke-width="3"/> <path d="m65.5 184.5v-169h69v169z" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
@ -756,9 +756,12 @@
<stop stop-color="#727171" offset=".89138"/> <stop stop-color="#727171" offset=".89138"/>
<stop stop-color="#727171" offset="1"/> <stop stop-color="#727171" offset="1"/>
</linearGradient> </linearGradient>
<pattern id="right-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/> <pattern id="base-right-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="top-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/> <pattern id="right-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#base-right-liquid)"/></pattern>
<pattern id="bottom-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/> <pattern id="base-top-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="top-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#base-top-liquid)"/></pattern>
<pattern id="base-bottom-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="bottom-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#base-bottom-liquid)"/></pattern>
<clipPath id="clip0_1245_66636"> <clipPath id="clip0_1245_66636">
<rect width="200" height="200" fill="#fff"/> <rect width="200" height="200" fill="#fff"/>
</clipPath> </clipPath>

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 58 KiB

14
application/src/main/data/json/system/scada_symbols/sand-filter.svg

@ -8,7 +8,7 @@
], ],
"widgetSizeX": 3, "widgetSizeX": 3,
"widgetSizeY": 5, "widgetSizeY": 5,
"stateRenderFunction": "var running = ctx.values.running;\nif (running) {\n ctx.api.enable(svg.children());\n} else {\n ctx.api.disable(svg.children());\n}", "stateRenderFunction": "var running = ctx.values.running;\nif (running) {\n ctx.api.enable(ctx.tags.filterMode);\n} else {\n ctx.api.disable(ctx.tags.filterMode);\n}",
"tags": [ "tags": [
{ {
"tag": "background", "tag": "background",
@ -354,37 +354,37 @@
<path d="m74 470c0-6.627 5.3726-12 12-12h186c6.627 0 12 5.373 12 12v56c0 6.627-5.373 12-12 12h-186c-6.6274 0-12-5.373-12-12v-56z" fill="#fff"/> <path d="m74 470c0-6.627 5.3726-12 12-12h186c6.627 0 12 5.373 12 12v56c0 6.627-5.373 12-12 12h-186c-6.6274 0-12-5.373-12-12v-56z" fill="#fff"/>
<path d="m75.5 470c0-5.799 4.701-10.5 10.5-10.5h186c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-186c-5.799 0-10.5-4.701-10.5-10.5v-56z" stroke="#198038" stroke-width="3"/> <path d="m75.5 470c0-5.799 4.701-10.5 10.5-10.5h186c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-186c-5.799 0-10.5-4.701-10.5-10.5v-56z" stroke="#198038" stroke-width="3"/>
<circle cx="179.5" cy="483" r="10" fill="#198038"/> <circle cx="179.5" cy="483" r="10" fill="#198038"/>
<text x="178.02383" y="512.61719" dominant-baseline="middle" fill="#000000" font-family="Roboto" font-size="28px" font-weight="500" text-anchor="middle" xml:space="preserve"><tspan>Filter</tspan></text> <text x="178.02383" y="512.61719" fill="#000000" font-family="Roboto" font-size="28px" font-weight="500" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">Filter</tspan></text>
</g> </g>
<g filter="url(#filter1_ii_1830_321985)"> <g filter="url(#filter1_ii_1830_321985)">
<path d="m74 574c0-6.627 5.3726-12 12-12h186c6.627 0 12 5.373 12 12v56c0 6.627-5.373 12-12 12h-186c-6.6274 0-12-5.373-12-12v-56z" fill="#fff"/> <path d="m74 574c0-6.627 5.3726-12 12-12h186c6.627 0 12 5.373 12 12v56c0 6.627-5.373 12-12 12h-186c-6.6274 0-12-5.373-12-12v-56z" fill="#fff"/>
<path d="m75.5 574c0-5.799 4.701-10.5 10.5-10.5h186c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-186c-5.799 0-10.5-4.701-10.5-10.5v-56z" stroke="#000" stroke-opacity=".12" stroke-width="3"/> <path d="m75.5 574c0-5.799 4.701-10.5 10.5-10.5h186c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-186c-5.799 0-10.5-4.701-10.5-10.5v-56z" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
<circle cx="179.5" cy="587" r="10" fill="#000" fill-opacity=".12"/> <circle cx="179.5" cy="587" r="10" fill="#000" fill-opacity=".12"/>
<text x="179.43652" y="617.61719" dominant-baseline="middle" fill="#727272" font-family="Roboto" font-size="28px" font-weight="500" text-anchor="middle" xml:space="preserve"><tspan>Backwash</tspan></text> <text x="179.43652" y="617.61719" fill="#727272" font-family="Roboto" font-size="28px" font-weight="500" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">Backwash</tspan></text>
</g> </g>
<g filter="url(#filter2_ii_1830_321985)"> <g filter="url(#filter2_ii_1830_321985)">
<path d="m74 678c0-6.627 5.3726-12 12-12h186c6.627 0 12 5.373 12 12v56c0 6.627-5.373 12-12 12h-186c-6.6274 0-12-5.373-12-12v-56z" fill="#fff"/> <path d="m74 678c0-6.627 5.3726-12 12-12h186c6.627 0 12 5.373 12 12v56c0 6.627-5.373 12-12 12h-186c-6.6274 0-12-5.373-12-12v-56z" fill="#fff"/>
<path d="m75.5 678c0-5.799 4.701-10.5 10.5-10.5h186c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-186c-5.799 0-10.5-4.701-10.5-10.5v-56z" stroke="#000" stroke-opacity=".12" stroke-width="3"/> <path d="m75.5 678c0-5.799 4.701-10.5 10.5-10.5h186c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-186c-5.799 0-10.5-4.701-10.5-10.5v-56z" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
<circle cx="179" cy="691" r="10" fill="#000" fill-opacity=".12"/> <circle cx="179" cy="691" r="10" fill="#000" fill-opacity=".12"/>
<text x="177.64941" y="722.61719" dominant-baseline="middle" fill="#727272" font-family="Roboto" font-size="28px" font-weight="500" text-anchor="middle" xml:space="preserve"><tspan>Rinse</tspan></text> <text x="177.64941" y="722.61719" fill="#727272" font-family="Roboto" font-size="28px" font-weight="500" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">Rinse</tspan></text>
</g> </g>
<g filter="url(#filter3_ii_1830_321985)"> <g filter="url(#filter3_ii_1830_321985)">
<path d="m316 470c0-6.627 5.373-12 12-12h186c6.627 0 12 5.373 12 12v56c0 6.627-5.373 12-12 12h-186c-6.627 0-12-5.373-12-12v-56z" fill="#fff"/> <path d="m316 470c0-6.627 5.373-12 12-12h186c6.627 0 12 5.373 12 12v56c0 6.627-5.373 12-12 12h-186c-6.627 0-12-5.373-12-12v-56z" fill="#fff"/>
<path d="m317.5 470c0-5.799 4.701-10.5 10.5-10.5h186c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-186c-5.799 0-10.5-4.701-10.5-10.5v-56z" stroke="#000" stroke-opacity=".12" stroke-width="3"/> <path d="m317.5 470c0-5.799 4.701-10.5 10.5-10.5h186c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-186c-5.799 0-10.5-4.701-10.5-10.5v-56z" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
<circle cx="421" cy="483" r="10" fill="#000" fill-opacity=".12"/> <circle cx="421" cy="483" r="10" fill="#000" fill-opacity=".12"/>
<text x="420.99316" y="512.75586" dominant-baseline="middle" fill="#727272" font-family="Roboto" font-size="28px" font-weight="500" text-anchor="middle" xml:space="preserve"><tspan>Waste</tspan></text> <text x="420.99316" y="512.75586" fill="#727272" font-family="Roboto" font-size="28px" font-weight="500" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">Waste</tspan></text>
</g> </g>
<g filter="url(#filter4_ii_1830_321985)"> <g filter="url(#filter4_ii_1830_321985)">
<path d="m316 574c0-6.627 5.373-12 12-12h186c6.627 0 12 5.373 12 12v56c0 6.627-5.373 12-12 12h-186c-6.627 0-12-5.373-12-12v-56z" fill="#fff"/> <path d="m316 574c0-6.627 5.373-12 12-12h186c6.627 0 12 5.373 12 12v56c0 6.627-5.373 12-12 12h-186c-6.627 0-12-5.373-12-12v-56z" fill="#fff"/>
<path d="m317.5 574c0-5.799 4.701-10.5 10.5-10.5h186c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-186c-5.799 0-10.5-4.701-10.5-10.5v-56z" stroke="#000" stroke-opacity=".12" stroke-width="3"/> <path d="m317.5 574c0-5.799 4.701-10.5 10.5-10.5h186c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-186c-5.799 0-10.5-4.701-10.5-10.5v-56z" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
<circle cx="421.5" cy="587" r="10" fill="#000" fill-opacity=".12"/> <circle cx="421.5" cy="587" r="10" fill="#000" fill-opacity=".12"/>
<text x="420.14062" y="617.61719" dominant-baseline="middle" fill="#727272" font-family="Roboto" font-size="28px" font-weight="500" text-anchor="middle" xml:space="preserve"><tspan>Recirculate</tspan></text> <text x="420.14062" y="617.61719" fill="#727272" font-family="Roboto" font-size="28px" font-weight="500" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">Recirculate</tspan></text>
</g> </g>
<g filter="url(#filter5_ii_1830_321985)"> <g filter="url(#filter5_ii_1830_321985)">
<path d="m316 678c0-6.627 5.373-12 12-12h186c6.627 0 12 5.373 12 12v56c0 6.627-5.373 12-12 12h-186c-6.627 0-12-5.373-12-12v-56z" fill="#fff"/> <path d="m316 678c0-6.627 5.373-12 12-12h186c6.627 0 12 5.373 12 12v56c0 6.627-5.373 12-12 12h-186c-6.627 0-12-5.373-12-12v-56z" fill="#fff"/>
<path d="m317.5 678c0-5.799 4.701-10.5 10.5-10.5h186c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-186c-5.799 0-10.5-4.701-10.5-10.5v-56z" stroke="#000" stroke-opacity=".12" stroke-width="3"/> <path d="m317.5 678c0-5.799 4.701-10.5 10.5-10.5h186c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-186c-5.799 0-10.5-4.701-10.5-10.5v-56z" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
<circle cx="421.5" cy="691" r="10" fill="#000" fill-opacity=".12"/> <circle cx="421.5" cy="691" r="10" fill="#000" fill-opacity=".12"/>
<text x="421.45215" y="721.61719" dominant-baseline="middle" fill="#727272" font-family="Roboto" font-size="28px" font-weight="500" text-anchor="middle" xml:space="preserve"><tspan>Closed</tspan></text> <text x="421.45215" y="721.61719" fill="#727272" font-family="Roboto" font-size="28px" font-weight="500" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">Closed</tspan></text>
</g> </g>
</g><defs> </g><defs>
<filter id="filter0_ii_1830_321985" x="70" y="454" width="218" height="88" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"> <filter id="filter0_ii_1830_321985" x="70" y="454" width="218" height="88" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

2
application/src/main/data/json/system/scada_symbols/small-left-motor-pump.svg

@ -10,7 +10,7 @@
"tags": [ "tags": [
{ {
"tag": "background", "tag": "background",
"stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}", "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (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}",
"actions": null "actions": null
}, },
{ {

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

8
application/src/main/data/json/system/scada_symbols/small-right-motor-pump.svg

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

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

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="600" height="600" fill="none" version="1.1" viewBox="0 0 600 600"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="600" height="600" fill="none" version="1.1" viewBox="0 0 600 600"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Small spherical tank", "title": "Small spherical tank",
"description": "Small spherical tank with current volume value and level visualizations.", "description": "Small spherical tank with current volume value and level visualizations.",
"searchTags": [ "searchTags": [
@ -25,17 +24,17 @@
}, },
{ {
"tag": "fluid", "tag": "fluid",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*560; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*560; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n",
"actions": null "actions": null
}, },
{ {
"tag": "fluid-background", "tag": "fluid-background",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*560; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*560; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n",
"actions": null "actions": null
}, },
{ {
"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', 'dominant-baseline': 'middle', class: 'majorTickText'});\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.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(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 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}",
"actions": null "actions": null
}, },
{ {
@ -556,7 +555,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><circle cx="300" cy="300" r="300" fill="#E5E5E5" tb:tag="background"/><circle cx="300" cy="300" r="300" fill="url(#paint0_radial_1711_268272)"/><circle cx="300" cy="300" r="298.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><circle cx="300" cy="300" r="300" fill="#4A4848" fill-opacity=".5"/><mask id="mask0_1711_268272" x="16" y="16" width="568" height="568" style="mask-type:alpha" maskUnits="userSpaceOnUse"> }]]></tb:metadata>
<circle cx="300" cy="300" r="300" fill="#E5E5E5" tb:tag="background"/><circle cx="300" cy="300" r="300" fill="url(#paint0_radial_1711_268272)"/><circle cx="300" cy="300" r="298.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><circle cx="300" cy="300" r="300" fill="#4A4848" fill-opacity=".5"/><mask id="mask0_1711_268272" x="16" y="16" width="568" height="568" style="mask-type:alpha" maskUnits="userSpaceOnUse">
<circle cx="300" cy="300" r="284" fill="#D9D9D9"/> <circle cx="300" cy="300" r="284" fill="#D9D9D9"/>
</mask><g mask="url(#mask0_1711_268272)"> </mask><g mask="url(#mask0_1711_268272)">
<rect transform="scale(1,-1)" y="-584" width="600" height="200" fill="#1ec1f4" fill-opacity=".5" tb:tag="fluid-background"/> <rect transform="scale(1,-1)" y="-584" width="600" height="200" fill="#1ec1f4" fill-opacity=".5" tb:tag="fluid-background"/>
@ -604,7 +604,7 @@
</g><rect x="79" y="563" width="80" height="24" rx="7" fill="#fff"/><rect x="79" y="563" width="80" height="24" rx="7" fill="url(#paint145_linear_1711_268272)"/><rect x="80.5" y="564.5" width="77" height="21" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="441" y="563" width="80" height="24" rx="7" fill="#fff"/><rect x="441" y="563" width="80" height="24" rx="7" fill="url(#paint146_linear_1711_268272)"/><rect x="442.5" y="564.5" width="77" height="21" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m103 563v-38l32 25v13h-32z" fill="#fff"/><path d="m103 563v-38l32 25v13h-32z" fill="url(#paint147_linear_1711_268272)"/><path d="m104.5 561.5v-33.425l29 22.657v10.768h-29z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m465 563v-13l32-24v37h-32z" fill="#fff"/><path d="m465 563v-13l32-24v37h-32z" fill="url(#paint148_linear_1711_268272)"/><path d="m466.5 561.5v-10.75l29-21.75v32.5h-29z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><circle cx="513" cy="406" r="32" fill="url(#paint149_radial_1711_268272)"/><circle cx="513" cy="406" r="30.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><circle cx="87" cy="406" r="32" fill="url(#paint150_radial_1711_268272)"/><circle cx="87" cy="406" r="30.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m71 571v-165c0-8.837 7.1634-16 16-16s16 7.163 16 16v165h-32z" fill="#fff"/><path d="m71 571v-165c0-8.837 7.1634-16 16-16s16 7.163 16 16v165h-32z" fill="url(#paint151_linear_1711_268272)"/><path d="m72.5 569.5v-163.5c0-8.008 6.4919-14.5 14.5-14.5s14.5 6.492 14.5 14.5v163.5h-29z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m497 571v-165c0-8.837 7.163-16 16-16s16 7.163 16 16v165h-32z" fill="#fff"/><path d="m497 571v-165c0-8.837 7.163-16 16-16s16 7.163 16 16v165h-32z" fill="url(#paint152_linear_1711_268272)"/><path d="m498.5 569.5v-163.5c0-8.008 6.492-14.5 14.5-14.5s14.5 6.492 14.5 14.5v163.5h-29z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="37" y="571" width="100" height="29" rx="7" fill="#fff"/><rect x="37" y="571" width="100" height="29" rx="7" fill="url(#paint153_linear_1711_268272)"/><rect x="38.5" y="572.5" width="97" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="463" y="571" width="100" height="29" rx="7" fill="#fff"/><rect x="463" y="571" width="100" height="29" rx="7" fill="url(#paint154_linear_1711_268272)"/><rect x="464.5" y="572.5" width="97" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><g transform="translate(-201)" filter="url(#filter0_ii_1694_158298)" tb:tag="value-box"> </g><rect x="79" y="563" width="80" height="24" rx="7" fill="#fff"/><rect x="79" y="563" width="80" height="24" rx="7" fill="url(#paint145_linear_1711_268272)"/><rect x="80.5" y="564.5" width="77" height="21" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="441" y="563" width="80" height="24" rx="7" fill="#fff"/><rect x="441" y="563" width="80" height="24" rx="7" fill="url(#paint146_linear_1711_268272)"/><rect x="442.5" y="564.5" width="77" height="21" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m103 563v-38l32 25v13h-32z" fill="#fff"/><path d="m103 563v-38l32 25v13h-32z" fill="url(#paint147_linear_1711_268272)"/><path d="m104.5 561.5v-33.425l29 22.657v10.768h-29z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m465 563v-13l32-24v37h-32z" fill="#fff"/><path d="m465 563v-13l32-24v37h-32z" fill="url(#paint148_linear_1711_268272)"/><path d="m466.5 561.5v-10.75l29-21.75v32.5h-29z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><circle cx="513" cy="406" r="32" fill="url(#paint149_radial_1711_268272)"/><circle cx="513" cy="406" r="30.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><circle cx="87" cy="406" r="32" fill="url(#paint150_radial_1711_268272)"/><circle cx="87" cy="406" r="30.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m71 571v-165c0-8.837 7.1634-16 16-16s16 7.163 16 16v165h-32z" fill="#fff"/><path d="m71 571v-165c0-8.837 7.1634-16 16-16s16 7.163 16 16v165h-32z" fill="url(#paint151_linear_1711_268272)"/><path d="m72.5 569.5v-163.5c0-8.008 6.4919-14.5 14.5-14.5s14.5 6.492 14.5 14.5v163.5h-29z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m497 571v-165c0-8.837 7.163-16 16-16s16 7.163 16 16v165h-32z" fill="#fff"/><path d="m497 571v-165c0-8.837 7.163-16 16-16s16 7.163 16 16v165h-32z" fill="url(#paint152_linear_1711_268272)"/><path d="m498.5 569.5v-163.5c0-8.008 6.492-14.5 14.5-14.5s14.5 6.492 14.5 14.5v163.5h-29z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="37" y="571" width="100" height="29" rx="7" fill="#fff"/><rect x="37" y="571" width="100" height="29" rx="7" fill="url(#paint153_linear_1711_268272)"/><rect x="38.5" y="572.5" width="97" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="463" y="571" width="100" height="29" rx="7" fill="#fff"/><rect x="463" y="571" width="100" height="29" rx="7" fill="url(#paint154_linear_1711_268272)"/><rect x="464.5" y="572.5" width="97" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><g transform="translate(-201)" filter="url(#filter0_ii_1694_158298)" tb:tag="value-box">
<path d="m381 272c0-6.627 5.373-12 12-12h216c6.627 0 12 5.373 12 12v56c0 6.627-5.373 12-12 12h-216c-6.627 0-12-5.373-12-12z" fill="#f3f3f3" tb:tag="value-box-background"/> <path d="m381 272c0-6.627 5.373-12 12-12h216c6.627 0 12 5.373 12 12v56c0 6.627-5.373 12-12 12h-216c-6.627 0-12-5.373-12-12z" fill="#f3f3f3" tb:tag="value-box-background"/>
<path d="m382.5 272c0-5.799 4.701-10.5 10.5-10.5h216c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-216c-5.799 0-10.5-4.701-10.5-10.5z" stroke="#727171" stroke-width="3"/> <path d="m382.5 272c0-5.799 4.701-10.5 10.5-10.5h216c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-216c-5.799 0-10.5-4.701-10.5-10.5z" stroke="#727171" stroke-width="3"/>
<text x="500.74219" y="300.07812" dominant-baseline="middle" fill="#727171" font-family="Roboto" font-size="32px" font-weight="500" text-anchor="middle" tb:tag="value-text" xml:space="preserve"><tspan>1660 gal</tspan></text> <text x="500.74219" y="300.07812" fill="#727171" font-family="Roboto" font-size="32px" font-weight="500" tb:tag="value-text" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">1660 gal</tspan></text>
</g><path d="m201.79 0s-201.79 0-201.79 100.5v492.54c0 3.9768 5.3726 6.9605 12 6.9605h576c6.627 0 12-2.9838 12-6.9605v-492.54c0-100.5-198.21-100.5-198.21-100.5h-101.79zm201.21 121.8c-3.866 0-7.0002 1.8804-7.0002 4.2v450.6c0 2.3196 3.134 4.2 7.0002 4.2h44c3.866 0 7.0002-1.8804 7.0002-4.2v-450.6c0-2.3196-3.134-4.2-7.0002-4.2z" fill="#000" fill-opacity="0" tb:tag="clickArea"/><defs> </g><path d="m201.79 0s-201.79 0-201.79 100.5v492.54c0 3.9768 5.3726 6.9605 12 6.9605h576c6.627 0 12-2.9838 12-6.9605v-492.54c0-100.5-198.21-100.5-198.21-100.5h-101.79zm201.21 121.8c-3.866 0-7.0002 1.8804-7.0002 4.2v450.6c0 2.3196 3.134 4.2 7.0002 4.2h44c3.866 0 7.0002-1.8804 7.0002-4.2v-450.6c0-2.3196-3.134-4.2-7.0002-4.2z" fill="#000" fill-opacity="0" tb:tag="clickArea"/><defs>
<radialGradient id="paint0_radial_1711_268272" cx="0" cy="0" r="1" gradientTransform="translate(300 300.7) rotate(180.27) scale(300 300.7)" gradientUnits="userSpaceOnUse"> <radialGradient id="paint0_radial_1711_268272" cx="0" cy="0" r="1" gradientTransform="translate(300 300.7) rotate(180.27) scale(300 300.7)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".15" offset=".00034187"/> <stop stop-color="#fff" stop-opacity=".15" offset=".00034187"/>
@ -694,7 +694,7 @@
<feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.5 0"/> <feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.5 0"/>
<feBlend in2="effect1_innerShadow_1694_158298" result="effect2_innerShadow_1694_158298"/> <feBlend in2="effect1_innerShadow_1694_158298" result="effect2_innerShadow_1694_158298"/>
</filter> </filter>
<pattern id="liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid"> <pattern id="base-liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/> <circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/>
<circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/> <circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/>
<circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/> <circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/>
@ -842,6 +842,7 @@
<circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/> <circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/>
<path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/> <path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/>
</pattern> </pattern>
<pattern id="liquid" width="584" height="201" patternUnits="userSpaceOnUse"><rect width="584" height="201" x="0" y="0" stroke-width="0" fill="url(#base-liquid)"/></pattern>
<linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse"> <linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/> <stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".05" offset="1"/> <stop stop-opacity=".05" offset="1"/>

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 105 KiB

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

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="1e3" height="1e3" fill="none" version="1.1" viewBox="0 0 1e3 1e3"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="1e3" height="1e3" fill="none" version="1.1" viewBox="0 0 1e3 1e3"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Spherical tank", "title": "Spherical tank",
"description": "Spherical tank with current volume value and level visualizations.", "description": "Spherical tank with current volume value and level visualizations.",
"searchTags": [ "searchTags": [
@ -25,17 +24,17 @@
}, },
{ {
"tag": "fluid", "tag": "fluid",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*960; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*960; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n",
"actions": null "actions": null
}, },
{ {
"tag": "fluid-background", "tag": "fluid-background",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*960; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*960; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n",
"actions": null "actions": null
}, },
{ {
"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', 'dominant-baseline': 'middle', class: 'majorTickText'});\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.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(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 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}",
"actions": null "actions": null
}, },
{ {
@ -556,7 +555,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><path d="m1e3 500c0 276.14-223.86 500-500 500s-500-223.86-500-500 223.86-500 500-500 500 223.86 500 500z" fill="#E5E5E5" tb:tag="background"/><path d="m1e3 500c0 276.14-223.86 500-500 500s-500-223.86-500-500 223.86-500 500-500 500 223.86 500 500z" fill="url(#paint0_radial_1711_251491)"/><path d="m998.5 500c0 275.31-223.19 498.5-498.5 498.5s-498.5-223.19-498.5-498.5 223.19-498.5 498.5-498.5 498.5 223.19 498.5 498.5z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><circle cx="500" cy="500" r="500" fill="#4A4848" fill-opacity=".5"/><mask id="mask0_1711_251491" x="16" y="16" width="968" height="968" style="mask-type:alpha" maskUnits="userSpaceOnUse"> }]]></tb:metadata>
<path d="m1e3 500c0 276.14-223.86 500-500 500s-500-223.86-500-500 223.86-500 500-500 500 223.86 500 500z" fill="#E5E5E5" tb:tag="background"/><path d="m1e3 500c0 276.14-223.86 500-500 500s-500-223.86-500-500 223.86-500 500-500 500 223.86 500 500z" fill="url(#paint0_radial_1711_251491)"/><path d="m998.5 500c0 275.31-223.19 498.5-498.5 498.5s-498.5-223.19-498.5-498.5 223.19-498.5 498.5-498.5 498.5 223.19 498.5 498.5z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><circle cx="500" cy="500" r="500" fill="#4A4848" fill-opacity=".5"/><mask id="mask0_1711_251491" x="16" y="16" width="968" height="968" style="mask-type:alpha" maskUnits="userSpaceOnUse">
<circle cx="500" cy="500" r="484" fill="#D9D9D9"/> <circle cx="500" cy="500" r="484" fill="#D9D9D9"/>
</mask><g mask="url(#mask0_1711_251491)"> </mask><g mask="url(#mask0_1711_251491)">
<rect transform="scale(1,-1)" x="8" y="-984" width="984" height="200" fill="#1EC1F4" fill-opacity=".5" tb:tag="fluid-background"/> <rect transform="scale(1,-1)" x="8" y="-984" width="984" height="200" fill="#1EC1F4" fill-opacity=".5" tb:tag="fluid-background"/>
@ -634,7 +634,7 @@
</g><rect x="182" y="963" width="80" height="24" rx="7" fill="#fff"/><rect x="182" y="963" width="80" height="24" rx="7" fill="url(#paint245_linear_1711_251491)"/><rect x="183.5" y="964.5" width="77" height="21" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="738" y="963" width="80" height="24" rx="7" fill="#fff"/><rect x="738" y="963" width="80" height="24" rx="7" fill="url(#paint246_linear_1711_251491)"/><rect x="739.5" y="964.5" width="77" height="21" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m206 963v-59l32 22v37h-32z" fill="#fff"/><path d="m206 963v-59l32 22v37h-32z" fill="url(#paint247_linear_1711_251491)"/><path d="m207.5 961.5v-54.648l29 19.937v34.711h-29z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m762 963v-37l32-22v59h-32z" fill="#fff"/><path d="m762 963v-37l32-22v59h-32z" fill="url(#paint248_linear_1711_251491)"/><path d="m763.5 961.5v-34.711l29-19.937v54.648h-29z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><circle cx="810" cy="788" r="32" fill="url(#paint249_radial_1711_251491)"/><circle cx="810" cy="788" r="30.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><circle cx="190" cy="788" r="32" fill="url(#paint250_radial_1711_251491)"/><circle cx="190" cy="788" r="30.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m174 971v-183c0-8.837 7.163-16 16-16s16 7.163 16 16v183h-32z" fill="#fff"/><path d="m174 971v-183c0-8.837 7.163-16 16-16s16 7.163 16 16v183h-32z" fill="url(#paint251_linear_1711_251491)"/><path d="m175.5 969.5v-181.5c0-8.008 6.492-14.5 14.5-14.5s14.5 6.492 14.5 14.5v181.5h-29z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m794 971v-183c0-8.837 7.163-16 16-16s16 7.163 16 16v183h-32z" fill="#fff"/><path d="m794 971v-183c0-8.837 7.163-16 16-16s16 7.163 16 16v183h-32z" fill="url(#paint252_linear_1711_251491)"/><path d="m795.5 969.5v-181.5c0-8.008 6.492-14.5 14.5-14.5s14.5 6.492 14.5 14.5v181.5h-29z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="140" y="971" width="100" height="29" rx="7" fill="#fff"/><rect x="140" y="971" width="100" height="29" rx="7" fill="url(#paint253_linear_1711_251491)"/><rect x="141.5" y="972.5" width="97" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="760" y="971" width="100" height="29" rx="7" fill="#fff"/><rect x="760" y="971" width="100" height="29" rx="7" fill="url(#paint254_linear_1711_251491)"/><rect x="761.5" y="972.5" width="97" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><g transform="translate(-1.6394 200)" filter="url(#filter0_ii_1694_158298)" tb:tag="value-box"> </g><rect x="182" y="963" width="80" height="24" rx="7" fill="#fff"/><rect x="182" y="963" width="80" height="24" rx="7" fill="url(#paint245_linear_1711_251491)"/><rect x="183.5" y="964.5" width="77" height="21" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="738" y="963" width="80" height="24" rx="7" fill="#fff"/><rect x="738" y="963" width="80" height="24" rx="7" fill="url(#paint246_linear_1711_251491)"/><rect x="739.5" y="964.5" width="77" height="21" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m206 963v-59l32 22v37h-32z" fill="#fff"/><path d="m206 963v-59l32 22v37h-32z" fill="url(#paint247_linear_1711_251491)"/><path d="m207.5 961.5v-54.648l29 19.937v34.711h-29z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m762 963v-37l32-22v59h-32z" fill="#fff"/><path d="m762 963v-37l32-22v59h-32z" fill="url(#paint248_linear_1711_251491)"/><path d="m763.5 961.5v-34.711l29-19.937v54.648h-29z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><circle cx="810" cy="788" r="32" fill="url(#paint249_radial_1711_251491)"/><circle cx="810" cy="788" r="30.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><circle cx="190" cy="788" r="32" fill="url(#paint250_radial_1711_251491)"/><circle cx="190" cy="788" r="30.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m174 971v-183c0-8.837 7.163-16 16-16s16 7.163 16 16v183h-32z" fill="#fff"/><path d="m174 971v-183c0-8.837 7.163-16 16-16s16 7.163 16 16v183h-32z" fill="url(#paint251_linear_1711_251491)"/><path d="m175.5 969.5v-181.5c0-8.008 6.492-14.5 14.5-14.5s14.5 6.492 14.5 14.5v181.5h-29z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m794 971v-183c0-8.837 7.163-16 16-16s16 7.163 16 16v183h-32z" fill="#fff"/><path d="m794 971v-183c0-8.837 7.163-16 16-16s16 7.163 16 16v183h-32z" fill="url(#paint252_linear_1711_251491)"/><path d="m795.5 969.5v-181.5c0-8.008 6.492-14.5 14.5-14.5s14.5 6.492 14.5 14.5v181.5h-29z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="140" y="971" width="100" height="29" rx="7" fill="#fff"/><rect x="140" y="971" width="100" height="29" rx="7" fill="url(#paint253_linear_1711_251491)"/><rect x="141.5" y="972.5" width="97" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="760" y="971" width="100" height="29" rx="7" fill="#fff"/><rect x="760" y="971" width="100" height="29" rx="7" fill="url(#paint254_linear_1711_251491)"/><rect x="761.5" y="972.5" width="97" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><g transform="translate(-1.6394 200)" filter="url(#filter0_ii_1694_158298)" tb:tag="value-box">
<path d="m381 272c0-6.627 5.373-12 12-12h216c6.627 0 12 5.373 12 12v56c0 6.627-5.373 12-12 12h-216c-6.627 0-12-5.373-12-12z" fill="#f3f3f3" tb:tag="value-box-background"/> <path d="m381 272c0-6.627 5.373-12 12-12h216c6.627 0 12 5.373 12 12v56c0 6.627-5.373 12-12 12h-216c-6.627 0-12-5.373-12-12z" fill="#f3f3f3" tb:tag="value-box-background"/>
<path d="m382.5 272c0-5.799 4.701-10.5 10.5-10.5h216c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-216c-5.799 0-10.5-4.701-10.5-10.5z" stroke="#727171" stroke-width="3"/> <path d="m382.5 272c0-5.799 4.701-10.5 10.5-10.5h216c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-216c-5.799 0-10.5-4.701-10.5-10.5z" stroke="#727171" stroke-width="3"/>
<text x="499.84497" y="300.37811" dominant-baseline="middle" fill="#727171" font-family="Roboto" font-size="32px" font-weight="500" text-anchor="middle" tb:tag="value-text" xml:space="preserve"><tspan>1660 gal</tspan></text> <text x="499.84497" y="300.37811" fill="#727171" font-family="Roboto" font-size="32px" font-weight="500" tb:tag="value-text" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">1660 gal</tspan></text>
</g><path d="m336.32 0s-336.32 0-336.32 167.5v820.9c0 6.628 8.9543 11.601 20 11.601h960c11.045 0 20-4.973 20-11.601v-820.9c0-167.5-330.35-167.5-330.35-167.5h-169.65zm335.35 203c-6.4433 0-11.667 3.134-11.667 7v751c0 3.866 5.2233 7 11.667 7h73.333c6.4433 0 11.667-3.134 11.667-7v-751c0-3.866-5.2233-7-11.667-7z" fill="#000" fill-opacity="0" style="stroke-width:.91287" tb:tag="clickArea"/><defs> </g><path d="m336.32 0s-336.32 0-336.32 167.5v820.9c0 6.628 8.9543 11.601 20 11.601h960c11.045 0 20-4.973 20-11.601v-820.9c0-167.5-330.35-167.5-330.35-167.5h-169.65zm335.35 203c-6.4433 0-11.667 3.134-11.667 7v751c0 3.866 5.2233 7 11.667 7h73.333c6.4433 0 11.667-3.134 11.667-7v-751c0-3.866-5.2233-7-11.667-7z" fill="#000" fill-opacity="0" style="stroke-width:.91287" tb:tag="clickArea"/><defs>
<radialGradient id="paint0_radial_1711_251491" cx="0" cy="0" r="1" gradientTransform="translate(500 501.16) rotate(180.27) scale(500.01 501.17)" gradientUnits="userSpaceOnUse"> <radialGradient id="paint0_radial_1711_251491" cx="0" cy="0" r="1" gradientTransform="translate(500 501.16) rotate(180.27) scale(500.01 501.17)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".15" offset=".00034187"/> <stop stop-color="#fff" stop-opacity=".15" offset=".00034187"/>
@ -724,7 +724,7 @@
<feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.5 0"/> <feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.5 0"/>
<feBlend in2="effect1_innerShadow_1694_158298" result="effect2_innerShadow_1694_158298"/> <feBlend in2="effect1_innerShadow_1694_158298" result="effect2_innerShadow_1694_158298"/>
</filter> </filter>
<pattern id="liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid"> <pattern id="base-liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/> <circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/>
<circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/> <circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/>
<circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/> <circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/>
@ -872,6 +872,7 @@
<circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/> <circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/>
<path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/> <path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/>
</pattern> </pattern>
<pattern id="liquid" width="584" height="201" patternUnits="userSpaceOnUse"><rect width="584" height="201" x="0" y="0" stroke-width="0" fill="url(#base-liquid)"/></pattern>
<linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse"> <linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/> <stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".05" offset="1"/> <stop stop-opacity=".05" offset="1"/>

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 118 KiB

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

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="600" height="1200" fill="none" version="1.1" viewBox="0 0 600 1200"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="600" height="1200" fill="none" version="1.1" viewBox="0 0 600 1200"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Stand cylindrical tank", "title": "Stand cylindrical tank",
"description": "Stand cylindrical tank with current volume value and level visualizations.", "description": "Stand cylindrical tank with current volume value and level visualizations.",
"searchTags": [ "searchTags": [
@ -25,17 +24,17 @@
}, },
{ {
"tag": "fluid", "tag": "fluid",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*900; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*900; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n",
"actions": null "actions": null
}, },
{ {
"tag": "fluid-background", "tag": "fluid-background",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*900; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*900; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n",
"actions": null "actions": null
}, },
{ {
"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', 'dominant-baseline': 'middle', class: 'majorTickText'});\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.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(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 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}",
"actions": null "actions": null
}, },
{ {
@ -561,7 +560,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><path d="m0 27c0-6.6274 5.3726-12 12-12h576c6.627 0 12 5.3726 12 12v948.02c0 3.768-1.746 7.322-5.28 8.627-64.25 23.737-536.96 20.807-590.57-0.264-2.9213-1.148-4.1543-4.137-4.1543-7.276v-949.1z" fill="#E5E5E5" tb:tag="background"/><path d="m0 27c0-6.6274 5.3726-12 12-12h576c6.627 0 12 5.3726 12 12v948.02c0 3.768-1.746 7.322-5.28 8.627-64.25 23.737-536.96 20.807-590.57-0.264-2.9213-1.148-4.1543-4.137-4.1543-7.276v-949.1z" fill="url(#paint0_linear_1690_149726)"/><path d="m0 26.996c0-6.6274 5.3726-12 12-12h576c6.627 0 12 5.3726 12 12v948.08c0 3.487-1.49 6.781-4.751 8.015-58.505 22.139-525.21 22.609-589.71 1.011-3.7027-1.24-5.5357-4.859-5.5357-8.764v-948.34z" fill="#4A4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="8" y="-968" width="584" height="201" fill="#1ec1f4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="8" y="-968" width="584" height="201" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-150-inside-1_1690_149726" fill="white"> }]]></tb:metadata>
<path d="m0 27c0-6.6274 5.3726-12 12-12h576c6.627 0 12 5.3726 12 12v948.02c0 3.768-1.746 7.322-5.28 8.627-64.25 23.737-536.96 20.807-590.57-0.264-2.9213-1.148-4.1543-4.137-4.1543-7.276v-949.1z" fill="#E5E5E5" tb:tag="background"/><path d="m0 27c0-6.6274 5.3726-12 12-12h576c6.627 0 12 5.3726 12 12v948.02c0 3.768-1.746 7.322-5.28 8.627-64.25 23.737-536.96 20.807-590.57-0.264-2.9213-1.148-4.1543-4.137-4.1543-7.276v-949.1z" fill="url(#paint0_linear_1690_149726)"/><path d="m0 26.996c0-6.6274 5.3726-12 12-12h576c6.627 0 12 5.3726 12 12v948.08c0 3.487-1.49 6.781-4.751 8.015-58.505 22.139-525.21 22.609-589.71 1.011-3.7027-1.24-5.5357-4.859-5.5357-8.764v-948.34z" fill="#4A4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="8" y="-968" width="584" height="201" fill="#1ec1f4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="8" y="-968" width="584" height="201" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-150-inside-1_1690_149726" fill="white">
<path d="m12 20c-6.6274 0-12 5.3726-12 12v943.85c0 3.15 1.2395 6.145 4.1742 7.29 53.699 20.946 526.2 23.856 590.52 0.263 3.547-1.302 5.301-4.861 5.301-8.64v-942.77c0-6.6274-5.373-12-12-12h-576zm391 46c-3.866 0-7 3.134-7 7v888c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-888c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill-rule="evenodd"/> <path d="m12 20c-6.6274 0-12 5.3726-12 12v943.85c0 3.15 1.2395 6.145 4.1742 7.29 53.699 20.946 526.2 23.856 590.52 0.263 3.547-1.302 5.301-4.861 5.301-8.64v-942.77c0-6.6274-5.373-12-12-12h-576zm391 46c-3.866 0-7 3.134-7 7v888c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-888c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill-rule="evenodd"/>
</mask><g tb:tag="top-layer"> </mask><g tb:tag="top-layer">
<path d="m12 20c-6.6274 0-12 5.3726-12 12v943.85c0 3.15 1.2395 6.145 4.1742 7.29 53.699 20.946 526.2 23.856 590.52 0.263 3.547-1.302 5.301-4.861 5.301-8.64v-942.77c0-6.6274-5.373-12-12-12h-576zm391 46c-3.866 0-7 3.134-7 7v888c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-888c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill="#E5E5E5" fill-rule="evenodd" tb:tag="background"/> <path d="m12 20c-6.6274 0-12 5.3726-12 12v943.85c0 3.15 1.2395 6.145 4.1742 7.29 53.699 20.946 526.2 23.856 590.52 0.263 3.547-1.302 5.301-4.861 5.301-8.64v-942.77c0-6.6274-5.373-12-12-12h-576zm391 46c-3.866 0-7 3.134-7 7v888c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-888c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill="#E5E5E5" fill-rule="evenodd" tb:tag="background"/>
@ -634,7 +634,7 @@
</g><g transform="translate(-1,409)" filter="url(#filter0_ii_1687_130892)" tb:tag="value-box"> </g><g transform="translate(-1,409)" filter="url(#filter0_ii_1687_130892)" tb:tag="value-box">
<path d="m180 62c0-6.6274 5.373-12 12-12h216c6.627 0 12 5.3726 12 12v56c0 6.627-5.373 12-12 12h-216c-6.627 0-12-5.373-12-12z" fill="#f3f3f3" tb:tag="value-box-background"/> <path d="m180 62c0-6.6274 5.373-12 12-12h216c6.627 0 12 5.3726 12 12v56c0 6.627-5.373 12-12 12h-216c-6.627 0-12-5.373-12-12z" fill="#f3f3f3" tb:tag="value-box-background"/>
<path d="m192 51.5h216c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-216c-5.799 0-10.5-4.701-10.5-10.5v-56c0-5.799 4.701-10.5 10.5-10.5z" stroke="#727171" stroke-width="3"/> <path d="m192 51.5h216c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-216c-5.799 0-10.5-4.701-10.5-10.5v-56c0-5.799 4.701-10.5 10.5-10.5z" stroke="#727171" stroke-width="3"/>
<text x="301.5625" y="93.546875" dominant-baseline="middle" fill="#727171" font-family="Roboto" font-size="32px" font-weight="500" text-anchor="middle" tb:tag="value-text" xml:space="preserve"><tspan>1660 gal</tspan></text> <text x="301.5625" y="93.546875" fill="#727171" font-family="Roboto" font-size="32px" font-weight="500" tb:tag="value-text" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">1660 gal</tspan></text>
</g><ellipse cx="300" cy="16" rx="292" ry="16" fill="#5D5C5C"/><rect x="58" y="1163" width="80" height="24" rx="7" fill="#fff"/><rect x="58" y="1163" width="80" height="24" rx="7" fill="url(#paint147_linear_1690_149725)" style="fill:url(#paint147_linear_1690_149725)"/><rect x="59.5" y="1164.5" width="77" height="21" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="462" y="1163" width="80" height="24" rx="7" fill="#fff"/><rect x="462" y="1163" width="80" height="24" rx="7" fill="url(#paint148_linear_1690_149725)" style="fill:url(#paint148_linear_1690_149725)"/><rect x="463.5" y="1164.5" width="77" height="21" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="rotate(15 96 1004.5)" x="96" y="1004.5" width="423.92" height="8" fill="#727171"/><rect transform="rotate(-15 94 1114.2)" x="94" y="1114.2" width="423.92" height="8" fill="#727171"/><path d="m82 1163v-169l32 2v167z" fill="#fff"/><path d="m82 1163v-169l32 2v167z" fill="url(#paint149_linear_1690_149725)" style="fill:url(#paint149_linear_1690_149725)"/><path d="m83.5 1161.5v-165.9l29 1.812v164.09z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m486 1163v-167l32-2v169z" fill="#fff"/><path d="m486 1163v-167l32-2v169z" fill="url(#paint150_linear_1690_149725)" style="fill:url(#paint150_linear_1690_149725)"/><path d="m487.5 1161.5v-164.09l29-1.812v165.9z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="rotate(15 68.875 1013)" x="68.875" y="1013" width="483.9" height="12" fill="#838282"/><rect transform="rotate(-15 63.922 1138.8)" x="63.922" y="1138.8" width="487.78" height="12" fill="#838282"/><circle cx="534" cy="885" r="32" fill="url(#paint151_radial_1690_149725)" style="fill:url(#paint151_radial_1690_149725)"/><circle cx="534" cy="885" r="30.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><circle cx="66" cy="885" r="32" fill="url(#paint152_radial_1690_149725)" style="fill:url(#paint152_radial_1690_149725)"/><circle cx="66" cy="885" r="30.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m50 1171v-284c0-8.837 7.1634-16 16-16s16 7.163 16 16v284z" fill="#fff"/><path d="m50 1171v-284c0-8.837 7.1634-16 16-16s16 7.163 16 16v284z" fill="url(#paint153_linear_1690_149725)" style="fill:url(#paint153_linear_1690_149725)"/><path d="m51.5 1169.5v-282.5c0-8.008 6.4919-14.5 14.5-14.5s14.5 6.492 14.5 14.5v282.5z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m518 1171v-284c0-8.837 7.163-16 16-16s16 7.163 16 16v284z" fill="#fff"/><path d="m518 1171v-284c0-8.837 7.163-16 16-16s16 7.163 16 16v284z" fill="url(#paint154_linear_1690_149725)" style="fill:url(#paint154_linear_1690_149725)"/><path d="m519.5 1169.5v-282.5c0-8.008 6.492-14.5 14.5-14.5s14.5 6.492 14.5 14.5v282.5z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="16" y="1171" width="100" height="29" rx="7" fill="#fff"/><rect x="16" y="1171" width="100" height="29" rx="7" fill="url(#paint155_linear_1690_149725)" style="fill:url(#paint155_linear_1690_149725)"/><rect x="17.5" y="1172.5" width="97" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="484" y="1171" width="100" height="29" rx="7" fill="#fff"/><rect x="484" y="1171" width="100" height="29" rx="7" fill="url(#paint156_linear_1690_149725)" style="fill:url(#paint156_linear_1690_149725)"/><rect x="485.5" y="1172.5" width="97" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m201.79 0s-201.79 0-201.79 201v985.08c0 7.9536 5.3726 13.921 12 13.921h576c6.627 0 12-5.9676 12-13.921v-985.08c0-201-198.21-201-198.21-201h-101.79zm201.21 243.6c-3.866 0-7 3.7608-7 8.4v901.2c0 4.6392 3.134 8.4 7 8.4h44c3.866 0 7-3.7608 7-8.4v-901.2c0-4.6392-3.134-8.4-7-8.4z" fill="#000" fill-opacity="0" style="stroke-width:1.0954" tb:tag="clickArea"/><defs> </g><ellipse cx="300" cy="16" rx="292" ry="16" fill="#5D5C5C"/><rect x="58" y="1163" width="80" height="24" rx="7" fill="#fff"/><rect x="58" y="1163" width="80" height="24" rx="7" fill="url(#paint147_linear_1690_149725)" style="fill:url(#paint147_linear_1690_149725)"/><rect x="59.5" y="1164.5" width="77" height="21" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="462" y="1163" width="80" height="24" rx="7" fill="#fff"/><rect x="462" y="1163" width="80" height="24" rx="7" fill="url(#paint148_linear_1690_149725)" style="fill:url(#paint148_linear_1690_149725)"/><rect x="463.5" y="1164.5" width="77" height="21" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="rotate(15 96 1004.5)" x="96" y="1004.5" width="423.92" height="8" fill="#727171"/><rect transform="rotate(-15 94 1114.2)" x="94" y="1114.2" width="423.92" height="8" fill="#727171"/><path d="m82 1163v-169l32 2v167z" fill="#fff"/><path d="m82 1163v-169l32 2v167z" fill="url(#paint149_linear_1690_149725)" style="fill:url(#paint149_linear_1690_149725)"/><path d="m83.5 1161.5v-165.9l29 1.812v164.09z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m486 1163v-167l32-2v169z" fill="#fff"/><path d="m486 1163v-167l32-2v169z" fill="url(#paint150_linear_1690_149725)" style="fill:url(#paint150_linear_1690_149725)"/><path d="m487.5 1161.5v-164.09l29-1.812v165.9z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="rotate(15 68.875 1013)" x="68.875" y="1013" width="483.9" height="12" fill="#838282"/><rect transform="rotate(-15 63.922 1138.8)" x="63.922" y="1138.8" width="487.78" height="12" fill="#838282"/><circle cx="534" cy="885" r="32" fill="url(#paint151_radial_1690_149725)" style="fill:url(#paint151_radial_1690_149725)"/><circle cx="534" cy="885" r="30.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><circle cx="66" cy="885" r="32" fill="url(#paint152_radial_1690_149725)" style="fill:url(#paint152_radial_1690_149725)"/><circle cx="66" cy="885" r="30.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m50 1171v-284c0-8.837 7.1634-16 16-16s16 7.163 16 16v284z" fill="#fff"/><path d="m50 1171v-284c0-8.837 7.1634-16 16-16s16 7.163 16 16v284z" fill="url(#paint153_linear_1690_149725)" style="fill:url(#paint153_linear_1690_149725)"/><path d="m51.5 1169.5v-282.5c0-8.008 6.4919-14.5 14.5-14.5s14.5 6.492 14.5 14.5v282.5z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m518 1171v-284c0-8.837 7.163-16 16-16s16 7.163 16 16v284z" fill="#fff"/><path d="m518 1171v-284c0-8.837 7.163-16 16-16s16 7.163 16 16v284z" fill="url(#paint154_linear_1690_149725)" style="fill:url(#paint154_linear_1690_149725)"/><path d="m519.5 1169.5v-282.5c0-8.008 6.492-14.5 14.5-14.5s14.5 6.492 14.5 14.5v282.5z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="16" y="1171" width="100" height="29" rx="7" fill="#fff"/><rect x="16" y="1171" width="100" height="29" rx="7" fill="url(#paint155_linear_1690_149725)" style="fill:url(#paint155_linear_1690_149725)"/><rect x="17.5" y="1172.5" width="97" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="484" y="1171" width="100" height="29" rx="7" fill="#fff"/><rect x="484" y="1171" width="100" height="29" rx="7" fill="url(#paint156_linear_1690_149725)" style="fill:url(#paint156_linear_1690_149725)"/><rect x="485.5" y="1172.5" width="97" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m201.79 0s-201.79 0-201.79 201v985.08c0 7.9536 5.3726 13.921 12 13.921h576c6.627 0 12-5.9676 12-13.921v-985.08c0-201-198.21-201-198.21-201h-101.79zm201.21 243.6c-3.866 0-7 3.7608-7 8.4v901.2c0 4.6392 3.134 8.4 7 8.4h44c3.866 0 7-3.7608 7-8.4v-901.2c0-4.6392-3.134-8.4-7-8.4z" fill="#000" fill-opacity="0" style="stroke-width:1.0954" tb:tag="clickArea"/><defs>
<linearGradient id="paint0_linear_1690_149726" x1="600" x2=".018833" y1="510.96" y2="504.56" gradientUnits="userSpaceOnUse"> <linearGradient id="paint0_linear_1690_149726" x1="600" x2=".018833" y1="510.96" y2="504.56" gradientUnits="userSpaceOnUse">
<stop stop-color="#020202" stop-opacity=".35" offset="0"/> <stop stop-color="#020202" stop-opacity=".35" offset="0"/>
@ -654,7 +654,7 @@
<stop stop-color="#020202" stop-opacity=".12" offset=".76303"/> <stop stop-color="#020202" stop-opacity=".12" offset=".76303"/>
<stop stop-color="#020202" stop-opacity=".35" offset="1"/> <stop stop-color="#020202" stop-opacity=".35" offset="1"/>
</linearGradient> </linearGradient>
<pattern id="liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid"> <pattern id="base-liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/> <circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/>
<circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/> <circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/>
<circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/> <circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/>
@ -802,6 +802,7 @@
<circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/> <circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/>
<path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/> <path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/>
</pattern> </pattern>
<pattern id="liquid" width="584" height="201" patternUnits="userSpaceOnUse"><rect width="584" height="201" x="0" y="0" stroke-width="0" fill="url(#base-liquid)"/></pattern>
<linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse"> <linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/> <stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".05" offset="1"/> <stop stop-opacity=".05" offset="1"/>

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 120 KiB

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

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="1e3" height="800" fill="none" version="1.1" viewBox="0 0 1e3 800"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="1e3" height="800" fill="none" version="1.1" viewBox="0 0 1e3 800"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Stand horizontal tank", "title": "Stand horizontal tank",
"description": "Stand horizontal tank with current volume value and level visualizations.", "description": "Stand horizontal tank with current volume value and level visualizations.",
"searchTags": [ "searchTags": [
@ -25,17 +24,17 @@
}, },
{ {
"tag": "fluid", "tag": "fluid",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*568; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*568; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n",
"actions": null "actions": null
}, },
{ {
"tag": "fluid-background", "tag": "fluid-background",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*568; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*568; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n",
"actions": null "actions": null
}, },
{ {
"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', 'dominant-baseline': 'middle', class: 'majorTickText'});\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.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(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 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}",
"actions": null "actions": null
}, },
{ {
@ -556,7 +555,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><path d="m174 600c-174 0-174-151.63-174-239 4.9551e-6 -1.386 9.6938e-6 -2.741 1.4223e-5 -4.065 1.18e-8 -4e-3 -1.4223e-5 -113.93-1.4223e-5 -113.94 0-87.373-8.3346e-7 -243 174-243h653.5c173 0 172.5 156.06 172.5 243v118c0 86.939 0 239-172.5 239h-653.5z" fill="#E5E5E5" tb:tag="background"/><path d="m174 600c-174 0-174-151.63-174-239 4.9551e-6 -1.386 9.6938e-6 -2.741 1.4223e-5 -4.065 1.18e-8 -4e-3 -1.4223e-5 -113.93-1.4223e-5 -113.94 0-87.373-8.3346e-7 -243 174-243h653.5c173 0 172.5 156.06 172.5 243v118c0 86.939 0 239-172.5 239h-653.5z" fill="url(#paint0_linear_1694_158298)"/><path d="m177.27 0.4874c198.56-1.0967 560.65 1.9e-5 640.18 0 79.522-1.9e-5 183.55 15 183.55 220v169c0 192.5-105.53 211.5-184.55 211.5s-516.64-0.5-639.18-0.5c-122.53 0-176.55-68.001-176.55-219 5.26e-4 -151-6.59e-4 -49.502-2.47e-4 -119s-22.006-260.91 176.55-262.01z" fill="#4A4848" fill-opacity=".5"/><mask id="mask0_1694_158298" x="17" y="16" width="968" height="570" style="mask-type:alpha" maskUnits="userSpaceOnUse"> }]]></tb:metadata>
<path d="m174 600c-174 0-174-151.63-174-239 4.9551e-6 -1.386 9.6938e-6 -2.741 1.4223e-5 -4.065 1.18e-8 -4e-3 -1.4223e-5 -113.93-1.4223e-5 -113.94 0-87.373-8.3346e-7 -243 174-243h653.5c173 0 172.5 156.06 172.5 243v118c0 86.939 0 239-172.5 239h-653.5z" fill="#E5E5E5" tb:tag="background"/><path d="m174 600c-174 0-174-151.63-174-239 4.9551e-6 -1.386 9.6938e-6 -2.741 1.4223e-5 -4.065 1.18e-8 -4e-3 -1.4223e-5 -113.93-1.4223e-5 -113.94 0-87.373-8.3346e-7 -243 174-243h653.5c173 0 172.5 156.06 172.5 243v118c0 86.939 0 239-172.5 239h-653.5z" fill="url(#paint0_linear_1694_158298)"/><path d="m177.27 0.4874c198.56-1.0967 560.65 1.9e-5 640.18 0 79.522-1.9e-5 183.55 15 183.55 220v169c0 192.5-105.53 211.5-184.55 211.5s-516.64-0.5-639.18-0.5c-122.53 0-176.55-68.001-176.55-219 5.26e-4 -151-6.59e-4 -49.502-2.47e-4 -119s-22.006-260.91 176.55-262.01z" fill="#4A4848" fill-opacity=".5"/><mask id="mask0_1694_158298" x="17" y="16" width="968" height="570" style="mask-type:alpha" maskUnits="userSpaceOnUse">
<path d="m178.68 16h654.82c117.67 0 151.5 98.586 151.5 184.16v204.18c0 135.62-68.502 181.66-162.5 181.66h-627.98c-129 0-177.5-66.558-177.5-181.66s0.0023-129.32 0-212.19c-3e-4 -9.509-5.4994-176.16 161.66-176.16z" fill="#D9D9D9"/> <path d="m178.68 16h654.82c117.67 0 151.5 98.586 151.5 184.16v204.18c0 135.62-68.502 181.66-162.5 181.66h-627.98c-129 0-177.5-66.558-177.5-181.66s0.0023-129.32 0-212.19c-3e-4 -9.509-5.4994-176.16 161.66-176.16z" fill="#D9D9D9"/>
</mask><g mask="url(#mask0_1694_158298)"> </mask><g mask="url(#mask0_1694_158298)">
<rect transform="scale(1,-1)" x="9" y="-585" width="984" height="200" fill="#1EC1F4" fill-opacity=".5" tb:tag="fluid-background"/> <rect transform="scale(1,-1)" x="9" y="-585" width="984" height="200" fill="#1EC1F4" fill-opacity=".5" tb:tag="fluid-background"/>
@ -638,7 +638,7 @@
</mask><g filter="url(#filter0_ii_1694_158298)" tb:tag="value-box"> </mask><g filter="url(#filter0_ii_1694_158298)" tb:tag="value-box">
<path d="m381 272c0-6.627 5.373-12 12-12h216c6.627 0 12 5.373 12 12v56c0 6.627-5.373 12-12 12h-216c-6.627 0-12-5.373-12-12v-56z" fill="#f3f3f3" tb:tag="value-box-background"/> <path d="m381 272c0-6.627 5.373-12 12-12h216c6.627 0 12 5.373 12 12v56c0 6.627-5.373 12-12 12h-216c-6.627 0-12-5.373-12-12v-56z" fill="#f3f3f3" tb:tag="value-box-background"/>
<path d="m382.5 272c0-5.799 4.701-10.5 10.5-10.5h216c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-216c-5.799 0-10.5-4.701-10.5-10.5v-56z" stroke="#727171" stroke-width="3"/> <path d="m382.5 272c0-5.799 4.701-10.5 10.5-10.5h216c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-216c-5.799 0-10.5-4.701-10.5-10.5v-56z" stroke="#727171" stroke-width="3"/>
<text x="499.84497" y="300.37811" dominant-baseline="middle" fill="#727171" font-family="Roboto" font-size="32px" font-weight="500" text-anchor="middle" tb:tag="value-text" xml:space="preserve"><tspan>1660 gal</tspan></text> <text x="499.84497" y="300.37811" fill="#727171" font-family="Roboto" font-size="32px" font-weight="500" tb:tag="value-text" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">1660 gal</tspan></text>
</g><g transform="matrix(1 0 0 .5 .64 .00025)"> </g><g transform="matrix(1 0 0 .5 .64 .00025)">
<path d="m335.68-5e-4s-336.32 0-336.32 201v985.08c0 7.9536 8.9543 13.921 20 13.921h960c11.045 0 20-5.9676 20-13.921v-985.08c0-201-330.35-201-330.35-201h-169.65zm335.35 243.6c-6.4433 0-11.667 3.7608-11.667 8.4v901.2c0 4.6392 5.2233 8.4 11.667 8.4h73.333c6.4433 0 11.667-3.7608 11.667-8.4v-901.2c0-4.6392-5.2233-8.4-11.667-8.4z" fill="#000" fill-opacity="0" tb:tag="clickArea"/> <path d="m335.68-5e-4s-336.32 0-336.32 201v985.08c0 7.9536 8.9543 13.921 20 13.921h960c11.045 0 20-5.9676 20-13.921v-985.08c0-201-330.35-201-330.35-201h-169.65zm335.35 243.6c-6.4433 0-11.667 3.7608-11.667 8.4v901.2c0 4.6392 5.2233 8.4 11.667 8.4h73.333c6.4433 0 11.667-3.7608 11.667-8.4v-901.2c0-4.6392-5.2233-8.4-11.667-8.4z" fill="#000" fill-opacity="0" tb:tag="clickArea"/>
</g><rect x="108" y="763" width="80" height="24" rx="7" fill="#fff"/><rect x="108" y="763" width="80" height="24" rx="7" fill="url(#paint245_linear_1694_158297)" style="fill:url(#paint245_linear_1694_158297)"/><rect x="109.5" y="764.5" width="77" height="21" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="812" y="763" width="80" height="24" rx="7" fill="#fff"/><rect x="812" y="763" width="80" height="24" rx="7" fill="url(#paint246_linear_1694_158297)" style="fill:url(#paint246_linear_1694_158297)"/><rect x="813.5" y="764.5" width="77" height="21" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="rotate(10 150.46 600.73)" x="150.46" y="600.73" width="710.35" height="8" fill="#727171"/><rect transform="rotate(-10 145.52 726.54)" x="145.52" y="726.53" width="715.97" height="8" fill="#727171"/><path d="m132 763v-167l32 3v164z" fill="#fff"/><path d="m132 763v-167l32 3v164z" fill="url(#paint247_linear_1694_158297)" style="fill:url(#paint247_linear_1694_158297)"/><path d="m133.5 761.5v-163.85l29 2.719v161.13z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m836 763v-164l32-3v167z" fill="#fff"/><path d="m836 763v-164l32-3v167z" fill="url(#paint248_linear_1694_158297)" style="fill:url(#paint248_linear_1694_158297)"/><path d="m837.5 761.5v-161.13l29-2.719v163.85z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="rotate(10 117.91 607.86)" x="117.91" y="607.85" width="781.6" height="12" fill="#838282"/><rect transform="rotate(-10 110.33 744.6)" x="110.33" y="744.6" width="793.89" height="12" fill="#838282"/><circle cx="892" cy="485" r="32" fill="url(#paint249_radial_1694_158297)" style="fill:url(#paint249_radial_1694_158297)"/><circle cx="892" cy="485" r="30.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><circle cx="108" cy="485" r="32" fill="url(#paint250_radial_1694_158297)" style="fill:url(#paint250_radial_1694_158297)"/><circle cx="108" cy="485" r="30.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m92 771v-284c0-8.837 7.1634-16 16-16 8.837 0 16 7.163 16 16v284z" fill="#fff"/><path d="m92 771v-284c0-8.837 7.1634-16 16-16 8.837 0 16 7.163 16 16v284z" fill="url(#paint251_linear_1694_158297)" style="fill:url(#paint251_linear_1694_158297)"/><path d="m93.5 769.5v-282.5c0-8.008 6.4919-14.5 14.5-14.5 8.008 0 14.5 6.492 14.5 14.5v282.5z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m876 771v-284c0-8.837 7.163-16 16-16s16 7.163 16 16v284z" fill="#fff"/><path d="m876 771v-284c0-8.837 7.163-16 16-16s16 7.163 16 16v284z" fill="url(#paint252_linear_1694_158297)" style="fill:url(#paint252_linear_1694_158297)"/><path d="m877.5 769.5v-282.5c0-8.008 6.492-14.5 14.5-14.5s14.5 6.492 14.5 14.5v282.5z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="58" y="771" width="100" height="29" rx="7" fill="#fff"/><rect x="58" y="771" width="100" height="29" rx="7" fill="url(#paint253_linear_1694_158297)" style="fill:url(#paint253_linear_1694_158297)"/><rect x="59.5" y="772.5" width="97" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="842" y="771" width="100" height="29" rx="7" fill="#fff"/><rect x="842" y="771" width="100" height="29" rx="7" fill="url(#paint254_linear_1694_158297)" style="fill:url(#paint254_linear_1694_158297)"/><rect x="843.5" y="772.5" width="97" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><defs> </g><rect x="108" y="763" width="80" height="24" rx="7" fill="#fff"/><rect x="108" y="763" width="80" height="24" rx="7" fill="url(#paint245_linear_1694_158297)" style="fill:url(#paint245_linear_1694_158297)"/><rect x="109.5" y="764.5" width="77" height="21" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="812" y="763" width="80" height="24" rx="7" fill="#fff"/><rect x="812" y="763" width="80" height="24" rx="7" fill="url(#paint246_linear_1694_158297)" style="fill:url(#paint246_linear_1694_158297)"/><rect x="813.5" y="764.5" width="77" height="21" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="rotate(10 150.46 600.73)" x="150.46" y="600.73" width="710.35" height="8" fill="#727171"/><rect transform="rotate(-10 145.52 726.54)" x="145.52" y="726.53" width="715.97" height="8" fill="#727171"/><path d="m132 763v-167l32 3v164z" fill="#fff"/><path d="m132 763v-167l32 3v164z" fill="url(#paint247_linear_1694_158297)" style="fill:url(#paint247_linear_1694_158297)"/><path d="m133.5 761.5v-163.85l29 2.719v161.13z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m836 763v-164l32-3v167z" fill="#fff"/><path d="m836 763v-164l32-3v167z" fill="url(#paint248_linear_1694_158297)" style="fill:url(#paint248_linear_1694_158297)"/><path d="m837.5 761.5v-161.13l29-2.719v163.85z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="rotate(10 117.91 607.86)" x="117.91" y="607.85" width="781.6" height="12" fill="#838282"/><rect transform="rotate(-10 110.33 744.6)" x="110.33" y="744.6" width="793.89" height="12" fill="#838282"/><circle cx="892" cy="485" r="32" fill="url(#paint249_radial_1694_158297)" style="fill:url(#paint249_radial_1694_158297)"/><circle cx="892" cy="485" r="30.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><circle cx="108" cy="485" r="32" fill="url(#paint250_radial_1694_158297)" style="fill:url(#paint250_radial_1694_158297)"/><circle cx="108" cy="485" r="30.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m92 771v-284c0-8.837 7.1634-16 16-16 8.837 0 16 7.163 16 16v284z" fill="#fff"/><path d="m92 771v-284c0-8.837 7.1634-16 16-16 8.837 0 16 7.163 16 16v284z" fill="url(#paint251_linear_1694_158297)" style="fill:url(#paint251_linear_1694_158297)"/><path d="m93.5 769.5v-282.5c0-8.008 6.4919-14.5 14.5-14.5 8.008 0 14.5 6.492 14.5 14.5v282.5z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m876 771v-284c0-8.837 7.163-16 16-16s16 7.163 16 16v284z" fill="#fff"/><path d="m876 771v-284c0-8.837 7.163-16 16-16s16 7.163 16 16v284z" fill="url(#paint252_linear_1694_158297)" style="fill:url(#paint252_linear_1694_158297)"/><path d="m877.5 769.5v-282.5c0-8.008 6.492-14.5 14.5-14.5s14.5 6.492 14.5 14.5v282.5z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="58" y="771" width="100" height="29" rx="7" fill="#fff"/><rect x="58" y="771" width="100" height="29" rx="7" fill="url(#paint253_linear_1694_158297)" style="fill:url(#paint253_linear_1694_158297)"/><rect x="59.5" y="772.5" width="97" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="842" y="771" width="100" height="29" rx="7" fill="#fff"/><rect x="842" y="771" width="100" height="29" rx="7" fill="url(#paint254_linear_1694_158297)" style="fill:url(#paint254_linear_1694_158297)"/><rect x="843.5" y="772.5" width="97" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><defs>
@ -676,7 +676,7 @@
<stop stop-color="#020202" stop-opacity=".12" offset=".76303"/> <stop stop-color="#020202" stop-opacity=".12" offset=".76303"/>
<stop stop-color="#020202" stop-opacity=".35" offset="1"/> <stop stop-color="#020202" stop-opacity=".35" offset="1"/>
</linearGradient> </linearGradient>
<pattern id="liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid"> <pattern id="base-liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/> <circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/>
<circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/> <circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/>
<circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/> <circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/>
@ -824,6 +824,7 @@
<circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/> <circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/>
<path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/> <path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/>
</pattern> </pattern>
<pattern id="liquid" width="584" height="201" patternUnits="userSpaceOnUse"><rect width="584" height="201" x="0" y="0" stroke-width="0" fill="url(#base-liquid)"/></pattern>
<linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse"> <linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/> <stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".05" offset="1"/> <stop stop-opacity=".05" offset="1"/>

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 121 KiB

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

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="800" height="800" fill="none" version="1.1" viewBox="0 0 800 800"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="800" height="800" fill="none" version="1.1" viewBox="0 0 800 800"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Stand vertical short tank", "title": "Stand vertical short tank",
"description": "Stand vertical short tank with current volume value and level visualizations.", "description": "Stand vertical short tank with current volume value and level visualizations.",
"searchTags": [ "searchTags": [
@ -26,17 +25,17 @@
}, },
{ {
"tag": "fluid", "tag": "fluid",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 245});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*442; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 245 + height });\n }\n}\n", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 245});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*442; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 245 + height });\n }\n}\n",
"actions": null "actions": null
}, },
{ {
"tag": "fluid-background", "tag": "fluid-background",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*442; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*442; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n",
"actions": null "actions": null
}, },
{ {
"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', 'dominant-baseline': 'middle', class: 'majorTickText'});\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.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(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 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}",
"actions": null "actions": null
}, },
{ {
@ -562,7 +561,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><path d="m1 101.5c0-100.5 269.05-100.5 269.05-100.5h266.67s264.29 0 264.29 100.5v487.71c0 6.628-5.373 11.793-12 11.793h-776c-6.6274 0-12-5.165-12-11.793v-487.71z" fill="#E5E5E5" tb:tag="background"/><path d="m1 101.5c0-100.5 269.05-100.5 269.05-100.5h266.67s264.29 0 264.29 100.5v487.71c0 6.628-5.373 11.793-12 11.793h-776c-6.6274 0-12-5.165-12-11.793v-487.71z" fill="url(#paint0_linear_1693_189770)"/><path d="m1 125h800v464c0 6.627-5.373 12-12 12h-776c-6.6274 0-12-5.373-12-12v-464z" fill="#4A4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="9" y="-579" width="784" height="200" fill="#1ec1f4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="9" y="-579" width="784" height="200" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-199-inside-1_1693_189770" fill="white"> }]]></tb:metadata>
<path d="m1 101.5c0-100.5 269.05-100.5 269.05-100.5h266.67s264.29 0 264.29 100.5v487.71c0 6.628-5.373 11.793-12 11.793h-776c-6.6274 0-12-5.165-12-11.793v-487.71z" fill="#E5E5E5" tb:tag="background"/><path d="m1 101.5c0-100.5 269.05-100.5 269.05-100.5h266.67s264.29 0 264.29 100.5v487.71c0 6.628-5.373 11.793-12 11.793h-776c-6.6274 0-12-5.165-12-11.793v-487.71z" fill="url(#paint0_linear_1693_189770)"/><path d="m1 125h800v464c0 6.627-5.373 12-12 12h-776c-6.6274 0-12-5.373-12-12v-464z" fill="#4A4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="9" y="-579" width="784" height="200" fill="#1ec1f4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="9" y="-579" width="784" height="200" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-199-inside-1_1693_189770" fill="white">
<path d="m269.05 0s-269.05 0-269.05 100.5v487.71c0 6.628 5.3726 11.793 12 11.793h776c6.627 0 12-5.165 12-11.793v-487.71c0-100.5-264.29-100.5-264.29-100.5h-266.67zm316.95 135c-3.866 0-7 3.134-7 7v428c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-428c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill-rule="evenodd"/> <path d="m269.05 0s-269.05 0-269.05 100.5v487.71c0 6.628 5.3726 11.793 12 11.793h776c6.627 0 12-5.165 12-11.793v-487.71c0-100.5-264.29-100.5-264.29-100.5h-266.67zm316.95 135c-3.866 0-7 3.134-7 7v428c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-428c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill-rule="evenodd"/>
</mask><g tb:tag="top-layer"> </mask><g tb:tag="top-layer">
<path d="m269.05 0s-269.05 0-269.05 100.5v487.71c0 6.628 5.3726 11.793 12 11.793h776c6.627 0 12-5.165 12-11.793v-487.71c0-100.5-264.29-100.5-264.29-100.5h-266.67zm316.95 135c-3.866 0-7 3.134-7 7v428c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-428c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill="#E5E5E5" fill-rule="evenodd" tb:tag="background"/> <path d="m269.05 0s-269.05 0-269.05 100.5v487.71c0 6.628 5.3726 11.793 12 11.793h776c6.627 0 12-5.165 12-11.793v-487.71c0-100.5-264.29-100.5-264.29-100.5h-266.67zm316.95 135c-3.866 0-7 3.134-7 7v428c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-428c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill="#E5E5E5" fill-rule="evenodd" tb:tag="background"/>
@ -607,7 +607,7 @@
</mask><path d="m8 125c-3.866 0-7-3.134-7-7s3.134-7 7-7h786c3.866 0 7 3.134 7 7s-3.134 7-7 7h-786z" fill="#D9D9D9"/><path d="m8 114h786v-6h-786v6zm786 8h-786v6h786v-6zm4-4c0 2.209-1.791 4-4 4v6c5.523 0 10-4.477 10-10h-6zm-4-4c2.209 0 4 1.791 4 4h6c0-5.523-4.477-10-10-10v6zm-790 4c0-2.209 1.7909-4 4-4v-6c-5.5228 0-10 4.477-10 10h6zm-6 0c0 5.523 4.4772 10 10 10v-6c-2.2092 0-4-1.791-4-4h-6z" fill="#727171" mask="url(#path-234-inside-2_1693_189770)"/><g filter="url(#filter0_ii_1693_189770)" tb:tag="value-box"> </mask><path d="m8 125c-3.866 0-7-3.134-7-7s3.134-7 7-7h786c3.866 0 7 3.134 7 7s-3.134 7-7 7h-786z" fill="#D9D9D9"/><path d="m8 114h786v-6h-786v6zm786 8h-786v6h786v-6zm4-4c0 2.209-1.791 4-4 4v6c5.523 0 10-4.477 10-10h-6zm-4-4c2.209 0 4 1.791 4 4h6c0-5.523-4.477-10-10-10v6zm-790 4c0-2.209 1.7909-4 4-4v-6c-5.5228 0-10 4.477-10 10h6zm-6 0c0 5.523 4.4772 10 10 10v-6c-2.2092 0-4-1.791-4-4h-6z" fill="#727171" mask="url(#path-234-inside-2_1693_189770)"/><g filter="url(#filter0_ii_1693_189770)" tb:tag="value-box">
<path d="m281 31c0-6.6274 5.373-12 12-12h216c6.627 0 12 5.3726 12 12v56c0 6.6274-5.373 12-12 12h-216c-6.627 0-12-5.3726-12-12z" fill="#f3f3f3" tb:tag="value-box-background"/> <path d="m281 31c0-6.6274 5.373-12 12-12h216c6.627 0 12 5.3726 12 12v56c0 6.6274-5.373 12-12 12h-216c-6.627 0-12-5.3726-12-12z" fill="#f3f3f3" tb:tag="value-box-background"/>
<path d="m282.5 31c0-5.799 4.701-10.5 10.5-10.5h216c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-216c-5.799 0-10.5-4.701-10.5-10.5z" stroke="#727171" stroke-width="3"/> <path d="m282.5 31c0-5.799 4.701-10.5 10.5-10.5h216c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-216c-5.799 0-10.5-4.701-10.5-10.5z" stroke="#727171" stroke-width="3"/>
<text x="399.12082" y="62.601822" dominant-baseline="middle" fill="#727171" font-family="Roboto" font-size="32px" font-weight="500" text-anchor="middle" tb:tag="value-text" xml:space="preserve"><tspan>1660 gal</tspan></text> <text x="399.12082" y="62.601822" fill="#727171" font-family="Roboto" font-size="32px" font-weight="500" tb:tag="value-text" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">1660 gal</tspan></text>
</g><defs> </g><defs>
<filter id="filter0_ii_1693_189770" x="277" y="15" width="248" height="88" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"> <filter id="filter0_ii_1693_189770" x="277" y="15" width="248" height="88" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
<feFlood flood-opacity="0" result="BackgroundImageFix"/> <feFlood flood-opacity="0" result="BackgroundImageFix"/>
@ -643,7 +643,7 @@
<stop stop-color="#020202" stop-opacity=".12" offset=".76303"/> <stop stop-color="#020202" stop-opacity=".12" offset=".76303"/>
<stop stop-color="#020202" stop-opacity=".35" offset="1"/> <stop stop-color="#020202" stop-opacity=".35" offset="1"/>
</linearGradient> </linearGradient>
<pattern id="liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid"> <pattern id="base-liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/> <circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/>
<circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/> <circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/>
<circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/> <circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/>
@ -791,6 +791,7 @@
<circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/> <circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/>
<path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/> <path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/>
</pattern> </pattern>
<pattern id="liquid" width="584" height="201" patternUnits="userSpaceOnUse"><rect width="584" height="201" x="0" y="0" stroke-width="0" fill="url(#base-liquid)"/></pattern>
<linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse"> <linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/> <stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".05" offset="1"/> <stop stop-opacity=".05" offset="1"/>

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 108 KiB

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

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="600" height="1200" fill="none" version="1.1" viewBox="0 0 600 1200"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="600" height="1200" fill="none" version="1.1" viewBox="0 0 600 1200"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Stand vertical tank", "title": "Stand vertical tank",
"description": "Stand vertical tank with current volume value and level visualizations.", "description": "Stand vertical tank with current volume value and level visualizations.",
"searchTags": [ "searchTags": [
@ -25,17 +24,17 @@
}, },
{ {
"tag": "fluid", "tag": "fluid",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*765; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*765; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n",
"actions": null "actions": null
}, },
{ {
"tag": "fluid-background", "tag": "fluid-background",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*765; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*765; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n",
"actions": null "actions": null
}, },
{ {
"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', 'dominant-baseline': 'middle', class: 'majorTickText'});\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.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(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 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}",
"actions": null "actions": null
}, },
{ {
@ -561,7 +560,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><path d="m0 167.5c0-167.5 201.79-167.5 201.79-167.5h200s198.21 0 198.21 167.5v820.9c0 6.628-5.373 11.601-12 11.601h-576c-6.6274 0-12-4.973-12-11.601v-820.9z" fill="#e5e5e5" tb:tag="background"/><path d="m0 167.5c0-167.5 201.79-167.5 201.79-167.5h200s198.21 0 198.21 167.5v820.9c0 6.628-5.373 11.601-12 11.601h-576c-6.6274 0-12-4.973-12-11.601v-820.9z" fill="url(#paint0_linear_1687_130892)" style="fill:url(#paint0_linear_1687_130892)"/><path d="m0 181h600v807c0 6.627-5.373 12-12 12h-576c-6.6274 0-12-5.373-12-12z" fill="#4a4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="8" y="-968" width="584" height="201" fill="#1ec1f4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="8" y="-968" width="584" height="201" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-150-inside-1_1687_130892" fill="#ffffff"> }]]></tb:metadata>
<path d="m0 167.5c0-167.5 201.79-167.5 201.79-167.5h200s198.21 0 198.21 167.5v820.9c0 6.628-5.373 11.601-12 11.601h-576c-6.6274 0-12-4.973-12-11.601v-820.9z" fill="#e5e5e5" tb:tag="background"/><path d="m0 167.5c0-167.5 201.79-167.5 201.79-167.5h200s198.21 0 198.21 167.5v820.9c0 6.628-5.373 11.601-12 11.601h-576c-6.6274 0-12-4.973-12-11.601v-820.9z" fill="url(#paint0_linear_1687_130892)" style="fill:url(#paint0_linear_1687_130892)"/><path d="m0 181h600v807c0 6.627-5.373 12-12 12h-576c-6.6274 0-12-5.373-12-12z" fill="#4a4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="8" y="-968" width="584" height="201" fill="#1ec1f4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="8" y="-968" width="584" height="201" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-150-inside-1_1687_130892" fill="#ffffff">
<path d="m201.79 0s-201.79 0-201.79 167.5v820.9c0 6.628 5.3726 11.601 12 11.601h576c6.627 0 12-4.973 12-11.601v-820.9c0-167.5-198.21-167.5-198.21-167.5h-101.79zm201.21 203c-3.866 0-7 3.134-7 7v751c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-751c0-3.866-3.134-7-7-7z" clip-rule="evenodd" fill-rule="evenodd"/> <path d="m201.79 0s-201.79 0-201.79 167.5v820.9c0 6.628 5.3726 11.601 12 11.601h576c6.627 0 12-4.973 12-11.601v-820.9c0-167.5-198.21-167.5-198.21-167.5h-101.79zm201.21 203c-3.866 0-7 3.134-7 7v751c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-751c0-3.866-3.134-7-7-7z" clip-rule="evenodd" fill-rule="evenodd"/>
</mask><g tb:tag="top-layer"> </mask><g tb:tag="top-layer">
<path d="m201.79 0s-201.79 0-201.79 167.5v820.9c0 6.628 5.3726 11.601 12 11.601h576c6.627 0 12-4.973 12-11.601v-820.9c0-167.5-198.21-167.5-198.21-167.5h-101.79zm201.21 203c-3.866 0-7 3.134-7 7v751c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-751c0-3.866-3.134-7-7-7z" clip-rule="evenodd" fill="#e5e5e5" fill-rule="evenodd" tb:tag="background"/> <path d="m201.79 0s-201.79 0-201.79 167.5v820.9c0 6.628 5.3726 11.601 12 11.601h576c6.627 0 12-4.973 12-11.601v-820.9c0-167.5-198.21-167.5-198.21-167.5h-101.79zm201.21 203c-3.866 0-7 3.134-7 7v751c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-751c0-3.866-3.134-7-7-7z" clip-rule="evenodd" fill="#e5e5e5" fill-rule="evenodd" tb:tag="background"/>
@ -636,7 +636,7 @@
</mask><path d="m7 181c-3.866 0-7-3.134-7-7s3.134-7 7-7h586c3.866 0 7 3.134 7 7s-3.134 7-7 7z" fill="#d9d9d9"/><path d="m7 170h586v-6h-586zm586 8h-586v6h586zm4-4c0 2.209-1.791 4-4 4v6c5.523 0 10-4.477 10-10zm-4-4c2.209 0 4 1.791 4 4h6c0-5.523-4.477-10-10-10zm-590 4c0-2.209 1.7909-4 4-4v-6c-5.5228 0-10 4.477-10 10zm-6 0c0 5.523 4.4771 10 10 10v-6c-2.2091 0-4-1.791-4-4z" fill="#727171" mask="url(#path-215-inside-2_1687_130892)"/><g filter="url(#filter0_ii_1687_130892)" tb:tag="value-box"> </mask><path d="m7 181c-3.866 0-7-3.134-7-7s3.134-7 7-7h586c3.866 0 7 3.134 7 7s-3.134 7-7 7z" fill="#d9d9d9"/><path d="m7 170h586v-6h-586zm586 8h-586v6h586zm4-4c0 2.209-1.791 4-4 4v6c5.523 0 10-4.477 10-10zm-4-4c2.209 0 4 1.791 4 4h6c0-5.523-4.477-10-10-10zm-590 4c0-2.209 1.7909-4 4-4v-6c-5.5228 0-10 4.477-10 10zm-6 0c0 5.523 4.4771 10 10 10v-6c-2.2091 0-4-1.791-4-4z" fill="#727171" mask="url(#path-215-inside-2_1687_130892)"/><g filter="url(#filter0_ii_1687_130892)" tb:tag="value-box">
<path d="m180 62c0-6.6274 5.373-12 12-12h216c6.627 0 12 5.3726 12 12v56c0 6.627-5.373 12-12 12h-216c-6.627 0-12-5.373-12-12z" fill="#f3f3f3" tb:tag="value-box-background"/> <path d="m180 62c0-6.6274 5.373-12 12-12h216c6.627 0 12 5.3726 12 12v56c0 6.627-5.373 12-12 12h-216c-6.627 0-12-5.373-12-12z" fill="#f3f3f3" tb:tag="value-box-background"/>
<path d="m192 51.5h216c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-216c-5.799 0-10.5-4.701-10.5-10.5v-56c0-5.799 4.701-10.5 10.5-10.5z" stroke="#727171" stroke-width="3"/> <path d="m192 51.5h216c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-216c-5.799 0-10.5-4.701-10.5-10.5v-56c0-5.799 4.701-10.5 10.5-10.5z" stroke="#727171" stroke-width="3"/>
<text x="301.5625" y="93.546875" dominant-baseline="middle" fill="#727171" font-family="Roboto" font-size="32px" font-weight="500" text-anchor="middle" tb:tag="value-text" xml:space="preserve"><tspan>1660 gal</tspan></text> <text x="301.5625" y="93.546875" fill="#727171" font-family="Roboto" font-size="32px" font-weight="500" tb:tag="value-text" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">1660 gal</tspan></text>
</g><rect x="58" y="1163" width="80" height="24" rx="7" fill="#fff"/><rect x="58" y="1163" width="80" height="24" rx="7" fill="url(#paint147_linear_1687_130893)" style="fill:url(#paint147_linear_1687_130893)"/><rect x="59.5" y="1164.5" width="77" height="21" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="462" y="1163" width="80" height="24" rx="7" fill="#fff"/><rect x="462" y="1163" width="80" height="24" rx="7" fill="url(#paint148_linear_1687_130893)" style="fill:url(#paint148_linear_1687_130893)"/><rect x="463.5" y="1164.5" width="77" height="21" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="rotate(15 96 1004.5)" x="96" y="1004.5" width="423.92" height="8" fill="#727171"/><rect transform="rotate(-15 94 1114.2)" x="94" y="1114.2" width="423.92" height="8" fill="#727171"/><path d="m82 1163v-163h32v163z" fill="#fff"/><path d="m82 1163v-163h32v163z" fill="url(#paint149_linear_1687_130893)" style="fill:url(#paint149_linear_1687_130893)"/><path d="m83.5 1161.5v-160h29v160z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m486 1163v-163h32v163z" fill="#fff"/><path d="m486 1163v-163h32v163z" fill="url(#paint150_linear_1687_130893)" style="fill:url(#paint150_linear_1687_130893)"/><path d="m487.5 1161.5v-160h29v160z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="rotate(15 68.875 1013)" x="68.875" y="1013" width="483.9" height="12" fill="#838282"/><rect transform="rotate(-15 63.922 1138.8)" x="63.922" y="1138.8" width="487.78" height="12" fill="#838282"/><circle cx="534" cy="885" r="32" fill="url(#paint151_radial_1687_130893)" style="fill:url(#paint151_radial_1687_130893)"/><circle cx="534" cy="885" r="30.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><circle cx="66" cy="885" r="32" fill="url(#paint152_radial_1687_130893)" style="fill:url(#paint152_radial_1687_130893)"/><circle cx="66" cy="885" r="30.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m50 1171v-284c0-8.837 7.1634-16 16-16s16 7.163 16 16v284z" fill="#fff"/><path d="m50 1171v-284c0-8.837 7.1634-16 16-16s16 7.163 16 16v284z" fill="url(#paint153_linear_1687_130893)" style="fill:url(#paint153_linear_1687_130893)"/><path d="m51.5 1169.5v-282.5c0-8.008 6.4919-14.5 14.5-14.5s14.5 6.492 14.5 14.5v282.5z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m518 1171v-284c0-8.837 7.163-16 16-16s16 7.163 16 16v284z" fill="#fff"/><path d="m518 1171v-284c0-8.837 7.163-16 16-16s16 7.163 16 16v284z" fill="url(#paint154_linear_1687_130893)" style="fill:url(#paint154_linear_1687_130893)"/><path d="m519.5 1169.5v-282.5c0-8.008 6.492-14.5 14.5-14.5s14.5 6.492 14.5 14.5v282.5z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="16" y="1171" width="100" height="29" rx="7" fill="#fff"/><rect x="16" y="1171" width="100" height="29" rx="7" fill="url(#paint155_linear_1687_130893)" style="fill:url(#paint155_linear_1687_130893)"/><rect x="17.5" y="1172.5" width="97" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="484" y="1171" width="100" height="29" rx="7" fill="#fff"/><rect x="484" y="1171" width="100" height="29" rx="7" fill="url(#paint156_linear_1687_130893)" style="fill:url(#paint156_linear_1687_130893)"/><rect x="485.5" y="1172.5" width="97" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><defs> </g><rect x="58" y="1163" width="80" height="24" rx="7" fill="#fff"/><rect x="58" y="1163" width="80" height="24" rx="7" fill="url(#paint147_linear_1687_130893)" style="fill:url(#paint147_linear_1687_130893)"/><rect x="59.5" y="1164.5" width="77" height="21" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="462" y="1163" width="80" height="24" rx="7" fill="#fff"/><rect x="462" y="1163" width="80" height="24" rx="7" fill="url(#paint148_linear_1687_130893)" style="fill:url(#paint148_linear_1687_130893)"/><rect x="463.5" y="1164.5" width="77" height="21" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="rotate(15 96 1004.5)" x="96" y="1004.5" width="423.92" height="8" fill="#727171"/><rect transform="rotate(-15 94 1114.2)" x="94" y="1114.2" width="423.92" height="8" fill="#727171"/><path d="m82 1163v-163h32v163z" fill="#fff"/><path d="m82 1163v-163h32v163z" fill="url(#paint149_linear_1687_130893)" style="fill:url(#paint149_linear_1687_130893)"/><path d="m83.5 1161.5v-160h29v160z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m486 1163v-163h32v163z" fill="#fff"/><path d="m486 1163v-163h32v163z" fill="url(#paint150_linear_1687_130893)" style="fill:url(#paint150_linear_1687_130893)"/><path d="m487.5 1161.5v-160h29v160z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="rotate(15 68.875 1013)" x="68.875" y="1013" width="483.9" height="12" fill="#838282"/><rect transform="rotate(-15 63.922 1138.8)" x="63.922" y="1138.8" width="487.78" height="12" fill="#838282"/><circle cx="534" cy="885" r="32" fill="url(#paint151_radial_1687_130893)" style="fill:url(#paint151_radial_1687_130893)"/><circle cx="534" cy="885" r="30.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><circle cx="66" cy="885" r="32" fill="url(#paint152_radial_1687_130893)" style="fill:url(#paint152_radial_1687_130893)"/><circle cx="66" cy="885" r="30.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m50 1171v-284c0-8.837 7.1634-16 16-16s16 7.163 16 16v284z" fill="#fff"/><path d="m50 1171v-284c0-8.837 7.1634-16 16-16s16 7.163 16 16v284z" fill="url(#paint153_linear_1687_130893)" style="fill:url(#paint153_linear_1687_130893)"/><path d="m51.5 1169.5v-282.5c0-8.008 6.4919-14.5 14.5-14.5s14.5 6.492 14.5 14.5v282.5z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m518 1171v-284c0-8.837 7.163-16 16-16s16 7.163 16 16v284z" fill="#fff"/><path d="m518 1171v-284c0-8.837 7.163-16 16-16s16 7.163 16 16v284z" fill="url(#paint154_linear_1687_130893)" style="fill:url(#paint154_linear_1687_130893)"/><path d="m519.5 1169.5v-282.5c0-8.008 6.492-14.5 14.5-14.5s14.5 6.492 14.5 14.5v282.5z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="16" y="1171" width="100" height="29" rx="7" fill="#fff"/><rect x="16" y="1171" width="100" height="29" rx="7" fill="url(#paint155_linear_1687_130893)" style="fill:url(#paint155_linear_1687_130893)"/><rect x="17.5" y="1172.5" width="97" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="484" y="1171" width="100" height="29" rx="7" fill="#fff"/><rect x="484" y="1171" width="100" height="29" rx="7" fill="url(#paint156_linear_1687_130893)" style="fill:url(#paint156_linear_1687_130893)"/><rect x="485.5" y="1172.5" width="97" height="26" rx="5.5" stroke="#000" stroke-opacity=".12" stroke-width="3"/><defs>
<filter id="filter0_ii_1687_130892" x="176" y="46" width="248" height="88" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"> <filter id="filter0_ii_1687_130892" x="176" y="46" width="248" height="88" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
<feFlood flood-opacity="0" result="BackgroundImageFix"/> <feFlood flood-opacity="0" result="BackgroundImageFix"/>
@ -1243,7 +1243,7 @@
<stop stop-color="#fff" stop-opacity=".25" offset="0"/> <stop stop-color="#fff" stop-opacity=".25" offset="0"/>
<stop stop-opacity=".1" offset="1"/> <stop stop-opacity=".1" offset="1"/>
</linearGradient> </linearGradient>
<pattern id="liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid"> <pattern id="base-liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/> <circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/>
<circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/> <circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/>
<circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/> <circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/>
@ -1391,6 +1391,7 @@
<circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/> <circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/>
<path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/> <path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/>
</pattern> </pattern>
<pattern id="liquid" width="584" height="201" patternUnits="userSpaceOnUse"><rect width="584" height="201" x="0" y="0" stroke-width="0" fill="url(#base-liquid)"/></pattern>
<linearGradient id="paint147_linear_1687_130893" x1="59.818" x2="80.389" y1="1175" y2="1208.9" gradientUnits="userSpaceOnUse"> <linearGradient id="paint147_linear_1687_130893" x1="59.818" x2="80.389" y1="1175" y2="1208.9" gradientUnits="userSpaceOnUse">
<stop stop-color="#727171" stop-opacity=".2" offset="0"/> <stop stop-color="#727171" stop-opacity=".2" offset="0"/>
<stop stop-color="#727171" offset="1"/> <stop stop-color="#727171" offset="1"/>

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 120 KiB

15
application/src/main/data/json/system/scada_symbols/top-flow-meter.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="400" fill="none" version="1.1" viewBox="0 0 400 400"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="400" fill="none" version="1.1" viewBox="0 0 400 400"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Top flow meter", "title": "Top flow meter",
"description": "Top flow meter component used to display flow related value and render various states. Includes pipe fluid and leak visualizations.", "description": "Top flow meter component used to display flow related value and render various states. Includes pipe fluid and leak visualizations.",
"searchTags": [ "searchTags": [
@ -20,7 +19,7 @@
}, },
{ {
"tag": "border", "tag": "border",
"stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}", "stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (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}",
"actions": { "actions": {
"click": { "click": {
"actionFunction": "ctx.api.callAction(event, 'displayClick');" "actionFunction": "ctx.api.callAction(event, 'displayClick');"
@ -38,7 +37,7 @@
}, },
{ {
"tag": "fluid", "tag": "fluid",
"stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill');\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(ctx.values.flowAnimationSpeed);\n }\n} else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}", "stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill').first();\n\nvar fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(ctx.values.flowAnimationSpeed);\n }\n }\n} else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}",
"actions": null "actions": null
}, },
{ {
@ -733,7 +732,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><rect x="14" y="264" width="372" height="72" fill="#fff" tb:tag="pipe-background"/><rect x="14" y="264" width="372" height="72" fill="url(#paint0_linear_1253_89545)"/><rect x="15.5" y="265.5" width="369" height="69" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="14" y="264" width="372" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect x="14" y="264" width="372" height="72" fill="url(#liquid)" stroke-width="0" style="display: none;" tb:tag="fluid"/><path d="M236 236V264.421L200 296L164 264.421V236H236Z" fill="#fff" tb:tag="pipe-background"/><path d="M236 236V264.421L200 296L164 264.421V236H236Z" fill="url(#paint1_linear_1253_89545)"/><path d="m234.5 237.5v26.242l-34.5 30.263-34.5-30.263v-26.242h69z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="387.5" y="251.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171" stroke-width="3"/><rect x="1.5" y="251.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171" stroke-width="3"/><mask id="path-7-inside-1_1253_89545" fill="white"> }]]></tb:metadata>
<rect x="14" y="264" width="372" height="72" fill="#fff" tb:tag="pipe-background"/><rect x="14" y="264" width="372" height="72" fill="url(#paint0_linear_1253_89545)"/><rect x="15.5" y="265.5" width="369" height="69" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="14" y="264" width="372" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect x="14" y="264" width="372" height="72" fill="url(#liquid)" stroke-width="0" style="display: none;" tb:tag="fluid"/><path d="M236 236V264.421L200 296L164 264.421V236H236Z" fill="#fff" tb:tag="pipe-background"/><path d="M236 236V264.421L200 296L164 264.421V236H236Z" fill="url(#paint1_linear_1253_89545)"/><path d="m234.5 237.5v26.242l-34.5 30.263-34.5-30.263v-26.242h69z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="387.5" y="251.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171" stroke-width="3"/><rect x="1.5" y="251.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171" stroke-width="3"/><mask id="path-7-inside-1_1253_89545" fill="white">
<path d="m325 125c0 69.036-55.964 125-125 125s-125-55.964-125-125 55.964-125 125-125 125 55.964 125 125zm-236.01 0c0 61.306 49.699 111 111.01 111s111-49.699 111-111c0-61.306-49.699-111.01-111-111.01s-111.01 49.699-111.01 111.01z"/> <path d="m325 125c0 69.036-55.964 125-125 125s-125-55.964-125-125 55.964-125 125-125 125 55.964 125 125zm-236.01 0c0 61.306 49.699 111 111.01 111s111-49.699 111-111c0-61.306-49.699-111.01-111-111.01s-111.01 49.699-111.01 111.01z"/>
</mask><path d="m325 125c0 69.036-55.964 125-125 125s-125-55.964-125-125 55.964-125 125-125 125 55.964 125 125zm-236.01 0c0 61.306 49.699 111 111.01 111s111-49.699 111-111c0-61.306-49.699-111.01-111-111.01s-111.01 49.699-111.01 111.01z" fill="#4A4848" mask="url(#path-7-inside-1_1253_89545)" stroke="#727171" stroke-width="6" tb:tag="border"/><circle cx="200" cy="125" r="111" fill="#fff" tb:tag="background"/><rect x="119.5" y="105.5" width="161" height="39" rx="3.8571" fill="#4A4848" fill-opacity=".06" tb:tag="border"/><path d="m123.36 104c-2.9419 0-5.3574 2.4155-5.3574 5.3574v31.285c0 2.9419 2.4155 5.3574 5.3574 5.3574h153.29c2.9419 0 5.3574-2.4155 5.3574-5.3574v-31.285c0-2.9419-2.4155-5.3574-5.3574-5.3574zm0 3h153.29c1.3318 0 2.3574 1.0256 2.3574 2.3574v31.285c0 1.3318-1.0256 2.3574-2.3574 2.3574h-153.29c-1.3318 0-2.3574-1.0256-2.3574-2.3574v-31.285c0-1.3318 1.0256-2.3574 2.3574-2.3574z" fill="#4A4848" tb:tag="border"/><circle cx="166" cy="73" r="16" fill="url(#paint2_linear_1253_89545)"/><circle cx="166" cy="73" r="15.5" stroke="#000" stroke-opacity=".12"/><circle cx="234" cy="73" r="16" fill="url(#paint3_linear_1253_89545)"/><circle cx="234" cy="73" r="15.5" stroke="#000" stroke-opacity=".12"/><defs> </mask><path d="m325 125c0 69.036-55.964 125-125 125s-125-55.964-125-125 55.964-125 125-125 125 55.964 125 125zm-236.01 0c0 61.306 49.699 111 111.01 111s111-49.699 111-111c0-61.306-49.699-111.01-111-111.01s-111.01 49.699-111.01 111.01z" fill="#4A4848" mask="url(#path-7-inside-1_1253_89545)" stroke="#727171" stroke-width="6" tb:tag="border"/><circle cx="200" cy="125" r="111" fill="#fff" tb:tag="background"/><rect x="119.5" y="105.5" width="161" height="39" rx="3.8571" fill="#4A4848" fill-opacity=".06" tb:tag="border"/><path d="m123.36 104c-2.9419 0-5.3574 2.4155-5.3574 5.3574v31.285c0 2.9419 2.4155 5.3574 5.3574 5.3574h153.29c2.9419 0 5.3574-2.4155 5.3574-5.3574v-31.285c0-2.9419-2.4155-5.3574-5.3574-5.3574zm0 3h153.29c1.3318 0 2.3574 1.0256 2.3574 2.3574v31.285c0 1.3318-1.0256 2.3574-2.3574 2.3574h-153.29c-1.3318 0-2.3574-1.0256-2.3574-2.3574v-31.285c0-1.3318 1.0256-2.3574 2.3574-2.3574z" fill="#4A4848" tb:tag="border"/><circle cx="166" cy="73" r="16" fill="url(#paint2_linear_1253_89545)"/><circle cx="166" cy="73" r="15.5" stroke="#000" stroke-opacity=".12"/><circle cx="234" cy="73" r="16" fill="url(#paint3_linear_1253_89545)"/><circle cx="234" cy="73" r="15.5" stroke="#000" stroke-opacity=".12"/><defs>
<linearGradient id="paint0_linear_1253_89545" x1="110.72" x2="110.63" y1="264" y2="336.01" gradientUnits="userSpaceOnUse"> <linearGradient id="paint0_linear_1253_89545" x1="110.72" x2="110.63" y1="264" y2="336.01" gradientUnits="userSpaceOnUse">
@ -768,7 +768,7 @@
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_1281_42354"/> <feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_1281_42354"/>
<feBlend in="SourceGraphic" in2="effect1_backgroundBlur_1281_42354" result="shape"/> <feBlend in="SourceGraphic" in2="effect1_backgroundBlur_1281_42354" result="shape"/>
</filter> </filter>
<pattern id="liquid" width="172" height="72" patternTransform="translate(14,-24)" patternUnits="userSpaceOnUse"> <pattern id="baseLiquid" width="172" height="72" patternTransform="translate(14,-24)" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/> <circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/> <circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/> <circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
@ -799,6 +799,7 @@
<circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/> <circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/>
<path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/> <path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/>
</pattern> </pattern>
<pattern id="liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#baseLiquid)"/></pattern>
<linearGradient id="paint28_linear_1182_32781-5" x1="19.316" x2="21" y1="8.2632" y2="23" gradientTransform="translate(-36,6)" gradientUnits="userSpaceOnUse"> <linearGradient id="paint28_linear_1182_32781-5" x1="19.316" x2="21" y1="8.2632" y2="23" gradientTransform="translate(-36,6)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/> <stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".15" offset="1"/> <stop stop-opacity=".15" offset="1"/>
@ -919,7 +920,7 @@
<stop stop-color="#727171" offset=".89138"/> <stop stop-color="#727171" offset=".89138"/>
<stop stop-color="#727171" offset="1"/> <stop stop-color="#727171" offset="1"/>
</linearGradient> </linearGradient>
</defs><text x="198.96094" y="126.94531" dominant-baseline="middle" fill="#000000" fill-opacity=".87" text-anchor="middle" style="font-family:Roboto;font-size:32px;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-weight:500" tb:tag="value" xml:space="preserve"><tspan>0</tspan></text><text x="199.47363" y="174.4729" dominant-baseline="middle" fill="#000000" fill-opacity=".87" text-anchor="middle" style="font-family:Roboto;font-size:22px;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-weight:500" tb:tag="valueUnits" xml:space="preserve"><tspan>m³/hr</tspan></text><g transform="translate(0 .00098038)" style="display: none;" tb:tag="broken"> </defs><text x="198.96094" y="126.94531" fill="#000000" fill-opacity=".87" style="font-family:Roboto;font-size:32px;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-weight:500" tb:tag="value" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">0</tspan></text><text x="199.47363" y="174.4729" fill="#000000" fill-opacity=".87" style="font-family:Roboto;font-size:22px;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-weight:500" tb:tag="valueUnits" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">m³/hr</tspan></text><g transform="translate(0 .00098038)" style="display: none;" tb:tag="broken">
<g filter="url(#filter0_b_1281_42354)" style=""> <g filter="url(#filter0_b_1281_42354)" style="">
<circle cx="200" cy="125" r="111" fill="#000" fill-opacity=".24" style=""/> <circle cx="200" cy="125" r="111" fill="#000" fill-opacity=".24" style=""/>
</g> </g>

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

50
application/src/main/data/json/system/scada_symbols/top-right-elbow-pipe.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Top right elbow pipe", "title": "Top right elbow pipe",
"description": "Top right elbow pipe with fluid and leak visualizations.", "description": "Top right elbow pipe with fluid and leak visualizations.",
"searchTags": [ "searchTags": [
@ -8,7 +7,7 @@
], ],
"widgetSizeX": 1, "widgetSizeX": 1,
"widgetSizeY": 1, "widgetSizeY": 1,
"stateRenderFunction": "var centerLiquidPattern = prepareLiquidPattern('center-fluid-background');\nvar horizontalLiquidPattern = prepareLiquidPattern('horizontal-fluid');\nvar verticalLiquidPattern = prepareLiquidPattern('vertical-fluid');\n\nvar fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\nvar flowAnimationSpeed = ctx.values.flowAnimationSpeed;\n \nif (horizontalLiquidPattern) {\n updateLiquidPatternAnimation(horizontalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (verticalLiquidPattern) {\n updateLiquidPatternAnimation(verticalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (centerLiquidPattern) {\n updateLiquidPatternAnimation(centerLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, true);\n}\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill');\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, fluid, flow, flowDirection, flowAnimationSpeed, center) {\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection, center);\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection, center);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(flowAnimationSpeed);\n }\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse, center) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n if (center) {\n var delta = deltaX * 1.17 * Math.cos(45*Math.PI/180);\n liquidPattern.animate(1000).ease('-').relative(delta, -delta).loop();\n } else {\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}\n\n", "stateRenderFunction": "var centerLiquidPattern = prepareLiquidPattern('center-fluid-background');\nvar horizontalLiquidPattern = prepareLiquidPattern('horizontal-fluid');\nvar verticalLiquidPattern = prepareLiquidPattern('vertical-fluid');\n\nvar fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\nvar flowAnimationSpeed = ctx.values.flowAnimationSpeed;\n \nif (horizontalLiquidPattern) {\n updateLiquidPatternAnimation(horizontalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (verticalLiquidPattern) {\n updateLiquidPatternAnimation(verticalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (centerLiquidPattern) {\n updateLiquidPatternAnimation(centerLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, true);\n}\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill').first();\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, fluid, flow, flowDirection, flowAnimationSpeed, center) {\n \n var fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n liquidPattern.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection, center);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection, center);\n }\n if (flow && fluidAnimation) {\n fluidAnimation.speed(flowAnimationSpeed);\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse, center) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n if (center) {\n var duration = 1000 * 1.17;\n return ctx.api.cssAnimate(liquidPattern, duration).relative(deltaX, 0).loop();\n } else {\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n }\n}",
"tags": [ "tags": [
{ {
"tag": "center-fluid", "tag": "center-fluid",
@ -251,7 +250,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><g transform="rotate(180,100,100)" clip-path="url(#clip0_1245_66459)"> }]]></tb:metadata>
<g transform="rotate(180,100,100)" clip-path="url(#clip0_1245_66459)">
<rect x="14" y="64" width="50" height="72" fill="#fff" tb:tag="pipe-background"/> <rect x="14" y="64" width="50" height="72" fill="#fff" tb:tag="pipe-background"/>
<rect x="14" y="64" width="50" height="72" fill="url(#paint0_linear_1245_66459)" style="fill:url(#paint0_linear_1245_66459)"/> <rect x="14" y="64" width="50" height="72" fill="url(#paint0_linear_1245_66459)" style="fill:url(#paint0_linear_1245_66459)"/>
<rect x="15.5" y="65.5" width="47" height="69" stroke="#000" stroke-opacity=".12" stroke-width="3"/> <rect x="15.5" y="65.5" width="47" height="69" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
@ -264,7 +264,7 @@
<path d="m64 64s30.518 1.7177 50.4 21.6c19.882 19.882 21.6 50.4 21.6 50.4h-72z" fill="url(#paint2_linear_1245_66459)" style="fill:url(#paint2_linear_1245_66459)"/> <path d="m64 64s30.518 1.7177 50.4 21.6c19.882 19.882 21.6 50.4 21.6 50.4h-72z" fill="url(#paint2_linear_1245_66459)" style="fill:url(#paint2_linear_1245_66459)"/>
<path d="m65.5 134.5v-68.865c0.8334 0.0861 1.9717 0.2211 3.3584 0.4273 3.1196 0.464 7.4889 1.2873 12.47 2.7228 9.9828 2.8767 22.316 8.1809 32.01 17.875 9.695 9.6942 14.999 22.027 17.875 32.01 1.436 4.982 2.259 9.351 2.723 12.471 0.206 1.386 0.341 2.525 0.428 3.358z" stroke="#000" stroke-opacity=".12" stroke-width="3"/> <path d="m65.5 134.5v-68.865c0.8334 0.0861 1.9717 0.2211 3.3584 0.4273 3.1196 0.464 7.4889 1.2873 12.47 2.7228 9.9828 2.8767 22.316 8.1809 32.01 17.875 9.695 9.6942 14.999 22.027 17.875 32.01 1.436 4.982 2.259 9.351 2.723 12.471 0.206 1.386 0.341 2.525 0.428 3.358z" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
</g><defs> </g><defs>
<pattern id="horizontal-liquid" width="172" height="72" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse"> <pattern id="liquid" width="172" height="72" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/> <circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/> <circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/> <circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
@ -415,37 +415,6 @@
<stop stop-color="#727171" offset=".89138"/> <stop stop-color="#727171" offset=".89138"/>
<stop stop-color="#727171" offset="1"/> <stop stop-color="#727171" offset="1"/>
</linearGradient> </linearGradient>
<pattern id="vertical-liquid" width="172" height="72" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
<circle transform="rotate(-90)" cx="-58" cy="34" r="8" fill="url(#paint31_linear_1182_32781-4)"/>
<circle transform="rotate(-90)" cx="-58" cy="155" r="8" fill="url(#paint32_linear_1182_32781-3)"/>
<circle transform="rotate(-90)" cx="-26" cy="33" r="5" fill="url(#paint33_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-26" cy="162" r="5" fill="url(#paint34_linear_1182_32781-4)"/>
<circle transform="rotate(-90)" cx="-37" cy="5" r="5" fill="url(#paint35_linear_1182_32781-6)"/>
<circle transform="rotate(-90)" cx="-8" cy="94" r="4" fill="url(#paint36_linear_1182_32781-9)"/>
<circle transform="rotate(-90)" cx="-60" cy="72" r="4" fill="url(#paint37_linear_1182_32781-4)"/>
<circle transform="rotate(-90)" cx="-37" cy="112" r="5" fill="url(#paint38_linear_1182_32781-2)"/>
<circle transform="rotate(-90)" cx="-39" cy="59" r="5" fill="url(#paint39_linear_1182_32781-2)"/>
<circle transform="rotate(-90)" cx="-62" cy="115" r="5" fill="url(#paint40_linear_1182_32781-6)"/>
<circle transform="rotate(-90)" cx="-42" cy="139" r="5" fill="url(#paint41_linear_1182_32781-4)"/>
<circle transform="rotate(-90)" cx="-21" cy="76" r="5" fill="url(#paint42_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-50.5" cy="126.5" r="2.5" fill="url(#paint43_linear_1182_32781-2)"/>
<circle transform="rotate(-90)" cx="-46.5" cy="169.5" r="2.5" fill="url(#paint44_linear_1182_32781-8)"/>
<circle transform="rotate(-90)" cx="-9.5" cy="57.5" r="2.5" fill="url(#paint45_linear_1182_32781-8)"/>
<circle transform="rotate(-90)" cx="-35.5" cy="96.5" r="2.5" fill="url(#paint46_linear_1182_32781-9)"/>
<circle transform="rotate(-90)" cx="-23.5" cy="91.5" r="2.5" fill="url(#paint47_linear_1182_32781-2)"/>
<circle transform="rotate(-90)" cx="-40.5" cy="22.5" r="2.5" fill="url(#paint48_linear_1182_32781-88)"/>
<circle transform="rotate(-90)" cx="-23.5" cy="124.5" r="2.5" fill="url(#paint49_linear_1182_32781-8)"/>
<circle transform="rotate(-90)" cx="-47.5" cy="86.5" r="2.5" fill="url(#paint50_linear_1182_32781-6)"/>
<circle transform="rotate(-90)" cx="-21.5" cy="51.5" r="2.5" fill="url(#paint51_linear_1182_32781-8)"/>
<circle transform="rotate(-90)" cx="-51.5" cy="48.5" r="2.5" fill="url(#paint52_linear_1182_32781-3)"/>
<circle transform="rotate(-90)" cx="-64" cy="14" r="4" fill="url(#paint53_linear_1182_32781-83)"/>
<circle transform="rotate(-90)" cx="-64" cy="135" r="4" fill="url(#paint54_linear_1182_32781-33)"/>
<circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/>
<path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/>
</pattern>
<clipPath id="clip0_1245_66459"> <clipPath id="clip0_1245_66459">
<rect width="200" height="200" fill="#fff"/> <rect width="200" height="200" fill="#fff"/>
</clipPath> </clipPath>
@ -530,9 +499,14 @@
<clipPath id="clipPath4208"> <clipPath id="clipPath4208">
<rect width="200" height="200" fill="#fff"/> <rect width="200" height="200" fill="#fff"/>
</clipPath> </clipPath>
<pattern id="center-liquid" patternTransform="scale(1.17) rotate(-45)" patternUnits="userSpaceOnUse" xlink:href="#horizontal-liquid"/> <pattern id="base-horizontal-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="horizontal-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect x="-172" width="688" height="72" fill="url(#base-horizontal-liquid)" stroke-width="0"/></pattern>
<pattern id="base-vertical-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="vertical-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect x="-172" width="688" height="72" fill="url(#base-vertical-liquid)" stroke-width="0"/></pattern>
<pattern id="base-center-liquid" width="172" height="72" patternTransform="translate(15)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="center-liquid" width="172" height="72" patternTransform="scale(1.17) rotate(-45)" patternUnits="userSpaceOnUse"><rect x="-172" width="688" height="72" fill="url(#base-center-liquid)" stroke-width="0"/></pattern>
</defs><rect x="136" y="64" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect transform="rotate(90)" x="14" y="-136" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect x="136" y="64" width="50" height="72" fill="url(#horizontal-liquid)" stroke-width="0" style="display: none;" tb:tag="horizontal-fluid"/><rect transform="rotate(90)" x="14" y="-136" width="50" height="72" fill="url(#vertical-liquid)" stroke-width="0" style="display: none;" tb:tag="vertical-fluid"/><path d="m136 63.85 0.1 72.05c-0.87135-0.09-2.1615-0.23117-3.6113-0.44675-3.2616-0.48513-7.8299-1.3459-13.038-2.8468-10.437-3.0077-23.332-8.5533-33.468-18.689-10.136-10.136-15.682-23.03-18.689-33.467-1.5014-5.2088-2.3619-9.7767-2.847-13.039-0.21538-1.4491-0.35652-2.6909-0.44748-3.5618z" fill="#1ec1f4" stroke="#000" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><g transform="matrix(0 .99929 -.99919 0 199.9 .095299)" style="display: none;" tb:tag="center-fluid"> </defs><rect x="136" y="64" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect transform="rotate(90)" x="14" y="-136" width="50" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect x="136" y="64" width="50" height="72" fill="url(#horizontal-liquid)" stroke-width="0" style="display: none;" tb:tag="horizontal-fluid"/><rect transform="rotate(90)" x="14" y="-136" width="50" height="72" fill="url(#vertical-liquid)" stroke-width="0" style="display: none;" tb:tag="vertical-fluid"/><path d="m136 63.85 0.1 72.05c-0.87135-0.09-2.1615-0.23117-3.6113-0.44675-3.2616-0.48513-7.8299-1.3459-13.038-2.8468-10.437-3.0077-23.332-8.5533-33.468-18.689-10.136-10.136-15.682-23.03-18.689-33.467-1.5014-5.2088-2.3619-9.7767-2.847-13.039-0.21538-1.4491-0.35652-2.6909-0.44748-3.5618z" fill="#1ec1f4" stroke="#000" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><g transform="matrix(0 .99929 -.99919 0 199.9 .095299)" style="display: none;" tb:tag="center-fluid">
<path d="m63.956 63.95 72.051-0.10008c-0.0901 0.87205-0.23133 2.1632-0.44707 3.6142-0.48547 3.2643-1.3469 7.8362-2.8488 13.048-3.0098 10.446-8.5594 23.351-18.702 33.495-10.143 10.145-23.046 15.695-33.491 18.704-5.2125 1.5026-9.7836 2.3638-13.048 2.8493-1.4501 0.21555-2.7428 0.3568-3.6144 0.44784z" fill="url(#center-liquid)" stroke="#000" stroke-width="0" tb:tag="center-fluid-background" style=""/> <path d="m63.956 63.95 72.051-0.10008c-0.0901 0.87205-0.23133 2.1632-0.44707 3.6142-0.48547 3.2643-1.3469 7.8362-2.8488 13.048-3.0098 10.446-8.5594 23.351-18.702 33.495-10.143 10.145-23.046 15.695-33.491 18.704-5.2125 1.5026-9.7836 2.3638-13.048 2.8493-1.4501 0.21555-2.7428 0.3568-3.6144 0.44784z" fill="url(#center-liquid)" stroke="#000" stroke-width="0" style="" tb:tag="center-fluid-background"/>
<path d="m132.05 63.75s-0.0216 31.824-17.622 50.677c-17.814 17.618-50.678 17.628-50.678 17.628" fill="none" stroke="#727171" stroke-width="7.9" style=""/> <path d="m132.05 63.75s-0.0216 31.824-17.622 50.677c-17.814 17.618-50.678 17.628-50.678 17.628" fill="none" stroke="#727171" stroke-width="7.9" style=""/>
<path d="m63.95 63.954v-0.17285l7.9056-0.02731v8.1066h-8.2058v-7.9066z" style="fill: rgb(114, 113, 113); stroke-width: 1.007;"/> <path d="m63.95 63.954v-0.17285l7.9056-0.02731v8.1066h-8.2058v-7.9066z" style="fill: rgb(114, 113, 113); stroke-width: 1.007;"/>
</g><g transform="rotate(180 100 100)" style="display: none;" tb:tag="leak"> </g><g transform="rotate(180 100 100)" style="display: none;" tb:tag="leak">

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 49 KiB

17
application/src/main/data/json/system/scada_symbols/top-tee-pipe.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Top tee pipe", "title": "Top tee pipe",
"description": "Top tee pipe with configurable left/right/top fluid and leak visualizations.", "description": "Top tee pipe with configurable left/right/top fluid and leak visualizations.",
"searchTags": [ "searchTags": [
@ -8,7 +7,7 @@
], ],
"widgetSizeX": 1, "widgetSizeX": 1,
"widgetSizeY": 1, "widgetSizeY": 1,
"stateRenderFunction": "var leftLiquidPattern = prepareLiquidPattern('left-fluid');\nvar rightLiquidPattern = prepareLiquidPattern('right-fluid');\nvar topLiquidPattern = prepareLiquidPattern('top-fluid');\n\nupdateLiquidPatternAnimation(leftLiquidPattern, 'left');\nupdateLiquidPatternAnimation(rightLiquidPattern, 'right');\nupdateLiquidPatternAnimation(topLiquidPattern, 'top');\n\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill');\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, prefix) {\n if (liquidPattern) {\n var fluid = ctx.values[prefix + 'Fluid'] && !ctx.values.leak;\n var flow = ctx.values[prefix + 'Flow'];\n var flowDirection = ctx.values[prefix + 'FlowDirection'];\n var flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(flowAnimationSpeed);\n }\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n}\n\n", "stateRenderFunction": "var leftLiquidPattern = prepareLiquidPattern('left-fluid');\nvar rightLiquidPattern = prepareLiquidPattern('right-fluid');\nvar topLiquidPattern = prepareLiquidPattern('top-fluid');\n\nupdateLiquidPatternAnimation(leftLiquidPattern, 'left');\nupdateLiquidPatternAnimation(rightLiquidPattern, 'right');\nupdateLiquidPatternAnimation(topLiquidPattern, 'top');\n\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill').first();\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, prefix) {\n if (liquidPattern) {\n var fluid = ctx.values[prefix + 'Fluid'] && !ctx.values.leak;\n var flow = ctx.values[prefix + 'Flow'];\n var flowDirection = ctx.values[prefix + 'FlowDirection'];\n var flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n var fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(flowAnimationSpeed);\n }\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}\n",
"tags": [ "tags": [
{ {
"tag": "leak", "tag": "leak",
@ -576,7 +575,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><g transform="translate(-4)" clip-path="url(#clip0_1245_66653)"> }]]></tb:metadata>
<g transform="translate(-4)" clip-path="url(#clip0_1245_66653)">
<path d="m18 64h172v72h-172z" fill="#fff" tb:tag="pipe-background"/> <path d="m18 64h172v72h-172z" fill="#fff" tb:tag="pipe-background"/>
<path d="m18 64h172v72h-172z" fill="url(#paint0_linear_1245_66653)" style="fill:url(#paint0_linear_1245_66653)"/> <path d="m18 64h172v72h-172z" fill="url(#paint0_linear_1245_66653)" style="fill:url(#paint0_linear_1245_66653)"/>
<path d="m19.5 65.5h169v69h-169z" stroke="#000" stroke-opacity=".12" stroke-width="3"/> <path d="m19.5 65.5h169v69h-169z" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
@ -738,9 +738,12 @@
<stop stop-color="#727171" offset=".89138"/> <stop stop-color="#727171" offset=".89138"/>
<stop stop-color="#727171" offset="1"/> <stop stop-color="#727171" offset="1"/>
</linearGradient> </linearGradient>
<pattern id="left-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/> <pattern id="base-left-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="right-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/> <pattern id="left-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#base-left-liquid)"/></pattern>
<pattern id="top-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/> <pattern id="base-right-liquid" patternTransform="translate(14,-8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="right-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#base-right-liquid)"/></pattern>
<pattern id="base-top-liquid" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse" xlink:href="#liquid"/>
<pattern id="top-liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#base-top-liquid)"/></pattern>
<clipPath id="clip0_1245_66653"> <clipPath id="clip0_1245_66653">
<rect transform="translate(4)" width="200" height="200" fill="#fff"/> <rect transform="translate(4)" width="200" height="200" fill="#fff"/>
</clipPath> </clipPath>

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 57 KiB

10
application/src/main/data/json/system/scada_symbols/vertical-ball-valve.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Vertical ball valve", "title": "Vertical ball valve",
"description": "Vertical ball valve with open/close animation and state colors.", "description": "Vertical ball valve with open/close animation and state colors.",
"searchTags": [ "searchTags": [
@ -11,7 +10,7 @@
"tags": [ "tags": [
{ {
"tag": "background", "tag": "background",
"stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar color = opened ? ctx.properties.openedColor : ctx.properties.closedColor;\nif (!openAnimate) {\n element.attr({fill: color});\n} else {\n ctx.api.animate(element, 500).attr({fill: color});\n element.remember('openAnimate', false);\n}\n", "stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar color = opened ? ctx.properties.openedColor : ctx.properties.closedColor;\nif (!openAnimate) {\n element.attr({fill: color});\n} else {\n ctx.api.cssAnimate(element, 500).attr({fill: color});\n element.remember('openAnimate', false);\n}\n",
"actions": null "actions": null
}, },
{ {
@ -25,7 +24,7 @@
}, },
{ {
"tag": "wheel", "tag": "wheel",
"stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar angle = opened ? ctx.properties.openedRotationAngle : ctx.properties.closedRotationAngle;\nif (!openAnimate) {\n element.transform({rotate: angle, originY: 100});\n} else {\n ctx.api.animate(element, 500).transform({rotate: angle, originY: 100});\n element.remember('openAnimate', false);\n}\n", "stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar angle = opened ? ctx.properties.openedRotationAngle : ctx.properties.closedRotationAngle;\nif (!openAnimate) {\n element.transform({rotate: angle, originY: 100});\n} else {\n ctx.api.cssAnimate(element, 500).transform({rotate: angle, originY: 100});\n element.remember('openAnimate', false);\n}\n",
"actions": null "actions": null
} }
], ],
@ -200,7 +199,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><defs> }]]></tb:metadata>
<defs>
<linearGradient id="paint0_linear_1595_99338" x1="58.72" x2="58.3" y1="46.5" y2="153.51" gradientTransform="rotate(-90,100,100)" gradientUnits="userSpaceOnUse"> <linearGradient id="paint0_linear_1595_99338" x1="58.72" x2="58.3" y1="46.5" y2="153.51" gradientTransform="rotate(-90,100,100)" gradientUnits="userSpaceOnUse">
<stop stop-color="#020202" stop-opacity=".35" offset="0"/> <stop stop-color="#020202" stop-opacity=".35" offset="0"/>
<stop stop-color="#020202" stop-opacity=".12" offset=".18316"/> <stop stop-color="#020202" stop-opacity=".12" offset=".18316"/>

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

15
application/src/main/data/json/system/scada_symbols/vertical-inline-flow-meter.svg

@ -1,5 +1,4 @@
<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": "Vertical inline flow meter", "title": "Vertical inline flow meter",
"description": "Vertical inline flow meter component used to display flow related value and render various states. Includes pipe fluid and leak visualizations.", "description": "Vertical inline flow meter component used to display flow related value and render various states. Includes pipe fluid and leak visualizations.",
"searchTags": [ "searchTags": [
@ -20,7 +19,7 @@
}, },
{ {
"tag": "border", "tag": "border",
"stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}", "stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (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}",
"actions": { "actions": {
"click": { "click": {
"actionFunction": "ctx.api.callAction(event, 'displayClick');" "actionFunction": "ctx.api.callAction(event, 'displayClick');"
@ -38,7 +37,7 @@
}, },
{ {
"tag": "fluid", "tag": "fluid",
"stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill');\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(ctx.values.flowAnimationSpeed);\n }\n} else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}", "stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill').first();\n\nvar fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(ctx.values.flowAnimationSpeed);\n }\n }\n} else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}",
"actions": null "actions": null
}, },
{ {
@ -733,7 +732,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><rect transform="rotate(-90)" x="-386" y="64" width="372" height="72" fill="#fff" tb:tag="pipe-background"/><rect transform="rotate(-90)" x="-386" y="64" width="372" height="72" fill="url(#paint0_linear_1595_101503)" style="fill:url(#paint0_linear_1595_101503)"/><rect transform="rotate(-90)" x="-384.5" y="65.5" width="369" height="69" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="rotate(-90)" x="-12.5" y="51.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><rect transform="rotate(-90)" x="-398.5" y="51.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><rect transform="rotate(90)" x="14" y="-136" width="372" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect transform="rotate(90)" x="14" y="-136" width="372" height="72" fill="url(#liquid)" stroke-width="0" style="display: none;" tb:tag="fluid"/><defs> }]]></tb:metadata>
<rect transform="rotate(-90)" x="-386" y="64" width="372" height="72" fill="#fff" tb:tag="pipe-background"/><rect transform="rotate(-90)" x="-386" y="64" width="372" height="72" fill="url(#paint0_linear_1595_101503)" style="fill:url(#paint0_linear_1595_101503)"/><rect transform="rotate(-90)" x="-384.5" y="65.5" width="369" height="69" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect transform="rotate(-90)" x="-12.5" y="51.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><rect transform="rotate(-90)" x="-398.5" y="51.5" width="11" height="97" rx="5.5" fill="#d9d9d9" stroke="#727171" stroke-width="3"/><rect transform="rotate(90)" x="14" y="-136" width="372" height="72" fill="#1ec1f4" stroke-width="0" style="display: none;" tb:tag="fluid-background"/><rect transform="rotate(90)" x="14" y="-136" width="372" height="72" fill="url(#liquid)" stroke-width="0" style="display: none;" tb:tag="fluid"/><defs>
<mask id="path-5-inside-1_1595_102361" fill="#ffffff"> <mask id="path-5-inside-1_1595_102361" fill="#ffffff">
<path d="m300 100c0 55.228-44.772 100-100 100s-100-44.772-100-100 44.772-100 100-100 100 44.772 100 100zm-188 0c0 48.603 39.401 88.004 88.004 88.004s88.004-39.401 88.004-88.004c0-48.604-39.401-88.004-88.004-88.004s-88.004 39.401-88.004 88.004z"/> <path d="m300 100c0 55.228-44.772 100-100 100s-100-44.772-100-100 44.772-100 100-100 100 44.772 100 100zm-188 0c0 48.603 39.401 88.004 88.004 88.004s88.004-39.401 88.004-88.004c0-48.604-39.401-88.004-88.004-88.004s-88.004 39.401-88.004 88.004z"/>
</mask> </mask>
@ -754,7 +754,7 @@
<stop stop-color="#727171" stop-opacity=".35" offset=".71855"/> <stop stop-color="#727171" stop-opacity=".35" offset=".71855"/>
<stop stop-color="#727171" offset="1"/> <stop stop-color="#727171" offset="1"/>
</linearGradient> </linearGradient>
<pattern id="liquid" width="172" height="72" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse"> <pattern id="baseLiquid" width="172" height="72" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/> <circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/> <circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/> <circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
@ -785,6 +785,7 @@
<circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/> <circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/>
<path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/> <path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/>
</pattern> </pattern>
<pattern id="liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#baseLiquid)"/></pattern>
<linearGradient id="paint28_linear_1182_32781-5" x1="19.316" x2="21" y1="8.2632" y2="23" gradientTransform="translate(-36,6)" gradientUnits="userSpaceOnUse"> <linearGradient id="paint28_linear_1182_32781-5" x1="19.316" x2="21" y1="8.2632" y2="23" gradientTransform="translate(-36,6)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/> <stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".15" offset="1"/> <stop stop-opacity=".15" offset="1"/>
@ -905,7 +906,7 @@
<stop stop-color="#727171" offset=".89138"/> <stop stop-color="#727171" offset=".89138"/>
<stop stop-color="#727171" offset="1"/> <stop stop-color="#727171" offset="1"/>
</linearGradient> </linearGradient>
</defs><path transform="translate(-100,100)" d="m300 100c0 55.228-44.772 100-100 100s-100-44.772-100-100 44.772-100 100-100 100 44.772 100 100zm-188 0c0 48.603 39.401 88.004 88.004 88.004s88.004-39.401 88.004-88.004c0-48.604-39.401-88.004-88.004-88.004s-88.004 39.401-88.004 88.004z" fill="#4a4848" mask="url(#path-5-inside-1_1595_102361)" stroke="#727171" stroke-width="6" tb:tag="border"/><circle cx="100" cy="200" r="88" fill="#fff" tb:tag="background"/><path d="m35.5 188.36c0-2.1302 1.727-3.8571 3.857-3.8571h121.29c2.13 0 3.857 1.7269 3.857 3.8571v23.286c0 2.13-1.727 3.857-3.857 3.857h-121.29c-2.13 0-3.857-1.727-3.857-3.857z" fill="#4a4848" fill-opacity=".06" tb:tag="border"/><path d="m39.36 183c-2.9408 0-5.3574 2.4165-5.3574 5.3574v23.285c0 2.9408 2.4166 5.3574 5.3574 5.3574h121.29c2.9408 0 5.3574-2.4166 5.3574-5.3574v-23.285c0-2.9409-2.4167-5.3574-5.3574-5.3574zm0 3h121.29c1.3192 0 2.3574 1.0379 2.3574 2.3574v23.285c0 1.3192-1.0382 2.3574-2.3574 2.3574h-121.29c-1.3192 0-2.3574-1.0382-2.3574-2.3574v-23.285c0-1.3195 1.0382-2.3574 2.3574-2.3574z" fill="#4a4848" tb:tag="border"/><circle cx="73.5" cy="159.5" r="12.5" fill="url(#paint1_linear_1595_102361)" style="fill:url(#paint1_linear_1595_102361)"/><circle cx="73.5" cy="159.5" r="12" stroke="#000" stroke-opacity=".12"/><circle cx="126.5" cy="159.5" r="12.5" fill="url(#paint2_linear_1595_102361)" style="fill:url(#paint2_linear_1595_102361)"/><circle cx="126.5" cy="159.5" r="12" stroke="#000" stroke-opacity=".12"/><text x="100.00586" y="202.19141" dominant-baseline="middle" fill="#000000" fill-opacity=".87" text-anchor="middle" style="font-family:Roboto;font-size:24px;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-weight:500" tb:tag="value" xml:space="preserve"><tspan>0</tspan></text><text x="99.617188" y="238.77344" dominant-baseline="middle" fill="#000000" fill-opacity=".87" text-anchor="middle" style="font-family:Roboto;font-size:16px;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-weight:500" tb:tag="valueUnits" xml:space="preserve"><tspan>m³/hr</tspan></text><g transform="translate(-100,100)" style="display: none;" tb:tag="broken"> </defs><path transform="translate(-100,100)" d="m300 100c0 55.228-44.772 100-100 100s-100-44.772-100-100 44.772-100 100-100 100 44.772 100 100zm-188 0c0 48.603 39.401 88.004 88.004 88.004s88.004-39.401 88.004-88.004c0-48.604-39.401-88.004-88.004-88.004s-88.004 39.401-88.004 88.004z" fill="#4a4848" mask="url(#path-5-inside-1_1595_102361)" stroke="#727171" stroke-width="6" tb:tag="border"/><circle cx="100" cy="200" r="88" fill="#fff" tb:tag="background"/><path d="m35.5 188.36c0-2.1302 1.727-3.8571 3.857-3.8571h121.29c2.13 0 3.857 1.7269 3.857 3.8571v23.286c0 2.13-1.727 3.857-3.857 3.857h-121.29c-2.13 0-3.857-1.727-3.857-3.857z" fill="#4a4848" fill-opacity=".06" tb:tag="border"/><path d="m39.36 183c-2.9408 0-5.3574 2.4165-5.3574 5.3574v23.285c0 2.9408 2.4166 5.3574 5.3574 5.3574h121.29c2.9408 0 5.3574-2.4166 5.3574-5.3574v-23.285c0-2.9409-2.4167-5.3574-5.3574-5.3574zm0 3h121.29c1.3192 0 2.3574 1.0379 2.3574 2.3574v23.285c0 1.3192-1.0382 2.3574-2.3574 2.3574h-121.29c-1.3192 0-2.3574-1.0382-2.3574-2.3574v-23.285c0-1.3195 1.0382-2.3574 2.3574-2.3574z" fill="#4a4848" tb:tag="border"/><circle cx="73.5" cy="159.5" r="12.5" fill="url(#paint1_linear_1595_102361)" style="fill:url(#paint1_linear_1595_102361)"/><circle cx="73.5" cy="159.5" r="12" stroke="#000" stroke-opacity=".12"/><circle cx="126.5" cy="159.5" r="12.5" fill="url(#paint2_linear_1595_102361)" style="fill:url(#paint2_linear_1595_102361)"/><circle cx="126.5" cy="159.5" r="12" stroke="#000" stroke-opacity=".12"/><text x="100.00586" y="202.19141" fill="#000000" fill-opacity=".87" style="font-family:Roboto;font-size:24px;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-weight:500" tb:tag="value" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">0</tspan></text><text x="99.617188" y="238.77344" fill="#000000" fill-opacity=".87" style="font-family:Roboto;font-size:16px;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-weight:500" tb:tag="valueUnits" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">m³/hr</tspan></text><g transform="translate(-100,100)" style="display: none;" tb:tag="broken">
<circle cx="200" cy="100" r="88" fill="#000" fill-opacity=".24" style=""/> <circle cx="200" cy="100" r="88" fill="#000" fill-opacity=".24" style=""/>
<path d="m253.92 84.931c-0.174 0.5228-0.309 1.1931-0.577 1.6622-0.577 1.6623-1.583 3.2037-2.991 4.1417-0.697 0.3484-1.488 0.6431-2.226 0.8439-1.971 0.6161-3.848 1.2858-5.725 1.9555-1.636 0.6832-3.513 1.3529-5.122 0.682 0.013-0.2414-0.027-0.3888-0.013-0.6302-2.977 0.4146-0.922 2.811-8.792 8.1516-11.067 2.815-27.05 6.112-29.128 6.044-0.724-0.041-1.408 0.066-2.038 0.08-1.113-0.014-2.172-0.122-3.218-0.471-0.764-0.188-1.515-0.617-2.225-0.899-2.038-0.792-4.183-1.396-6.448-1.571 0.603 0.47 1.206 0.939 1.809 1.409 0 0 0.121 0.442 0.081 0.295 0.201-0.134 0.482 0.027 0.616 0.228-0.751 0.442-1.502 0.884-2.199 1.232-3.647 2.024-7.389 3.993-11.304 5.614 3.875-0.897 7.683-2.33 11.411-4.058 0.845-0.389 1.595-0.831 2.574-1.018 2.306-0.549 4.666 0.551 6.824 1.785l0.094 0.054c0.187 0.107 0.469 0.268 0.455 0.509 0.081 0.295-0.228 0.617-0.483 0.845-1.368 1.085-2.642 2.225-3.916 3.364-1.582 1.461-2.883 3.082-4.56 4.489-1.475 1.274-3.084 2.346-4.64 3.324-0.75 0.442-1.408 0.938-2.239 1.085-0.885 0.241-1.756 0.241-2.668 0.093-1.153-0.161-2.212-0.269-3.311-0.524-6.314-1.37-17.674 5.248-23.787 2.873 2.44 1.395 5.027 2.75 7.466 4.145-3.66 0.522-7.133 2.022-10.7 3.469-0.992 0.428-1.984 0.857-3.111 1.085-0.884 0.241-1.903 0.281-2.775 0.28-1.743 0-3.526-0.148-4.973-1.101 0.831 0.724 1.769 1.261 2.667 1.65-0.255 0.228-0.563 0.55-0.818 0.778-2.146 2.01-4.198 4.074-6.29 5.99 1.14-0.469 2.199-1.232 3.165-2.05 1.274-1.139 2.226-2.587 3.768-3.324 1.743-0.87 3.808-0.561 5.806-0.789 3.164-0.307 6.154-1.834 9.131-3.12s5.228-3.036 10.264-4.758c3.63-1.241 9.543-3.131 12.411-2.486 0.912 0.148 1.864 0.443 2.762 0.832 0.617 0.228 1.18 0.55 1.836 0.926 0.751 0.429 1.408 0.805 2.212 1.14 0.469 0.268 0.992 0.443 1.475 0.47 0.576 0.08 1.22-0.174 1.716-0.388 1.783-0.724 3.567-1.447 5.35-2.17-0.295 0.08-0.737 0.2-1.032 0.281-2.36 0.642-4.666 1.191-6.972 0.869-0.483-0.027-1.006-0.202-1.327-0.51-0.094-0.054-0.188-0.107-0.134-0.201-0.362-0.456-0.214-1.368 0.322-1.435 0.282-0.71 1.046-1.394 1.556-1.849 4.157-3.351 8.408-6.647 12.606-9.85 0.456-0.362 0.912-0.724 1.555-0.978 0.644-0.255 1.18-0.322 1.81-0.335 4.572-0.374 19.939-3.129 23.095-4.335 5.049-1.929 5.348-0.685 7.45-3.208 0.335 0.067 7.054-8.7036 7.39-8.6365 2.252 0.4164 4.343 1.9858 6.099 3.4875 1.45 1.565-2.243 6.524-2.272 7.287-0.032 0.867-18.866 17.201-20.503 18.31-0.751 0.442-2.24 0.274-2.533 0.546-0.103 0.096-0.557 1.991 0.264 0.492-0.764-0.029-8.29 2.174-13.599 1.877-0.482-0.027-1.059-0.108-1.582-0.282-0.335-0.067-0.616-0.228-0.844-0.483-0.094-0.054-0.134-0.201-0.174-0.349-0.04-0.147 0.107-0.188 0.161-0.281 2.803-3.378 6.276-6.621 7.203-11.072-0.201 0.134-0.255 0.228-0.456 0.362-0.054 0.094-0.201 0.134-0.308 0.321-1.744 1.743-3.085 4.089-5.003 5.482-0.858 0.63-1.81 1.206-2.441 2.091-0.791 1.166-1.033 2.895-2.052 3.807-0.51 0.455-1.207 0.804-1.797 0.964-1.623 0.442-3.419 0.535-5.001 1.125-0.792 0.295-1.596 0.831-2.387 1.125-0.148 0.04-0.201 0.134-0.349 0.174-1.783 0.724-7.731 4.384-9.661 4.276 1.689 0.095 7.288-3.392 8.963-3.056-1.797 1.836-3.54 3.579-5.525 5.307 1.904-1.152 3.674-2.505 5.311-4.06 0.818-0.777 1.636-1.555 2.588-2.131 1.85-1.058 4.451-0.816 5.51 1.034 0.134 0.202 0.268 0.403 0.254 0.644 0.415 1.234 0.241 2.628 0.12 3.928-0.551 3.794-1.236 7.387-2.029 11.168-0.591 2.775-1.275 5.496-2.764 7.882-0.108 0.188-0.309 0.322-0.496 0.214 0.442 0.751 0.106 1.555-0.417 2.252-0.429 0.751-1.046 1.394-1.569 2.091-0.47 0.604-0.939 1.207-1.409 1.81-2.239 2.828-4.573 5.602-7.135 8.122 1.556-0.978 3.031-2.251 4.184-3.833 0.886-1.112 1.583-2.332 2.509-3.297 0.979-1.059 2.226-1.715 3.205-2.774 0.416-0.51 0.885-1.113 1.395-1.568 0.066 1.407 0.039 2.761 0.106 4.169 0.201-1.005 0.403-2.011 0.604-3.016 0.215-1.247 0.524-2.44 0.832-3.633 0.94-2.949 2.885-5.697 3.395-8.767 0.014-0.241 0.027-0.483 0.041-0.724-0.201 0.134-0.309 0.322-0.51 0.456-0.04-0.148 0.067-0.336 0.027-0.483 0.055-2.708 0.646-5.483 1.371-8.057 0.563-2.293 1.235-4.773 2.965-6.274 0.509-0.455 1.113-0.857 1.622-1.313 0.51-0.456 0.845-1.26 0.738-1.944 3.178 0.323 6.66 1.265 9.329-0.571 2.065-1.434 4.621-0.818 5.075-2.713-0.792 0.735 1.103 1.189 2.659 0.483s23.915-14.808 24.838-26.835c-0.911-1.8908-2.519-3.4332-4.316-4.2114-0.094-0.0537-0.094-0.0537-0.187-0.1073 2.923-1.1923 5.993-2.4246 8.796-4.0593 0.805-0.536 1.703-1.0184 2.28-1.8092 0.671-1.6086 0.832-3.633 0.94-5.5635z" fill="#5c5a5a" style=""/> <path d="m253.92 84.931c-0.174 0.5228-0.309 1.1931-0.577 1.6622-0.577 1.6623-1.583 3.2037-2.991 4.1417-0.697 0.3484-1.488 0.6431-2.226 0.8439-1.971 0.6161-3.848 1.2858-5.725 1.9555-1.636 0.6832-3.513 1.3529-5.122 0.682 0.013-0.2414-0.027-0.3888-0.013-0.6302-2.977 0.4146-0.922 2.811-8.792 8.1516-11.067 2.815-27.05 6.112-29.128 6.044-0.724-0.041-1.408 0.066-2.038 0.08-1.113-0.014-2.172-0.122-3.218-0.471-0.764-0.188-1.515-0.617-2.225-0.899-2.038-0.792-4.183-1.396-6.448-1.571 0.603 0.47 1.206 0.939 1.809 1.409 0 0 0.121 0.442 0.081 0.295 0.201-0.134 0.482 0.027 0.616 0.228-0.751 0.442-1.502 0.884-2.199 1.232-3.647 2.024-7.389 3.993-11.304 5.614 3.875-0.897 7.683-2.33 11.411-4.058 0.845-0.389 1.595-0.831 2.574-1.018 2.306-0.549 4.666 0.551 6.824 1.785l0.094 0.054c0.187 0.107 0.469 0.268 0.455 0.509 0.081 0.295-0.228 0.617-0.483 0.845-1.368 1.085-2.642 2.225-3.916 3.364-1.582 1.461-2.883 3.082-4.56 4.489-1.475 1.274-3.084 2.346-4.64 3.324-0.75 0.442-1.408 0.938-2.239 1.085-0.885 0.241-1.756 0.241-2.668 0.093-1.153-0.161-2.212-0.269-3.311-0.524-6.314-1.37-17.674 5.248-23.787 2.873 2.44 1.395 5.027 2.75 7.466 4.145-3.66 0.522-7.133 2.022-10.7 3.469-0.992 0.428-1.984 0.857-3.111 1.085-0.884 0.241-1.903 0.281-2.775 0.28-1.743 0-3.526-0.148-4.973-1.101 0.831 0.724 1.769 1.261 2.667 1.65-0.255 0.228-0.563 0.55-0.818 0.778-2.146 2.01-4.198 4.074-6.29 5.99 1.14-0.469 2.199-1.232 3.165-2.05 1.274-1.139 2.226-2.587 3.768-3.324 1.743-0.87 3.808-0.561 5.806-0.789 3.164-0.307 6.154-1.834 9.131-3.12s5.228-3.036 10.264-4.758c3.63-1.241 9.543-3.131 12.411-2.486 0.912 0.148 1.864 0.443 2.762 0.832 0.617 0.228 1.18 0.55 1.836 0.926 0.751 0.429 1.408 0.805 2.212 1.14 0.469 0.268 0.992 0.443 1.475 0.47 0.576 0.08 1.22-0.174 1.716-0.388 1.783-0.724 3.567-1.447 5.35-2.17-0.295 0.08-0.737 0.2-1.032 0.281-2.36 0.642-4.666 1.191-6.972 0.869-0.483-0.027-1.006-0.202-1.327-0.51-0.094-0.054-0.188-0.107-0.134-0.201-0.362-0.456-0.214-1.368 0.322-1.435 0.282-0.71 1.046-1.394 1.556-1.849 4.157-3.351 8.408-6.647 12.606-9.85 0.456-0.362 0.912-0.724 1.555-0.978 0.644-0.255 1.18-0.322 1.81-0.335 4.572-0.374 19.939-3.129 23.095-4.335 5.049-1.929 5.348-0.685 7.45-3.208 0.335 0.067 7.054-8.7036 7.39-8.6365 2.252 0.4164 4.343 1.9858 6.099 3.4875 1.45 1.565-2.243 6.524-2.272 7.287-0.032 0.867-18.866 17.201-20.503 18.31-0.751 0.442-2.24 0.274-2.533 0.546-0.103 0.096-0.557 1.991 0.264 0.492-0.764-0.029-8.29 2.174-13.599 1.877-0.482-0.027-1.059-0.108-1.582-0.282-0.335-0.067-0.616-0.228-0.844-0.483-0.094-0.054-0.134-0.201-0.174-0.349-0.04-0.147 0.107-0.188 0.161-0.281 2.803-3.378 6.276-6.621 7.203-11.072-0.201 0.134-0.255 0.228-0.456 0.362-0.054 0.094-0.201 0.134-0.308 0.321-1.744 1.743-3.085 4.089-5.003 5.482-0.858 0.63-1.81 1.206-2.441 2.091-0.791 1.166-1.033 2.895-2.052 3.807-0.51 0.455-1.207 0.804-1.797 0.964-1.623 0.442-3.419 0.535-5.001 1.125-0.792 0.295-1.596 0.831-2.387 1.125-0.148 0.04-0.201 0.134-0.349 0.174-1.783 0.724-7.731 4.384-9.661 4.276 1.689 0.095 7.288-3.392 8.963-3.056-1.797 1.836-3.54 3.579-5.525 5.307 1.904-1.152 3.674-2.505 5.311-4.06 0.818-0.777 1.636-1.555 2.588-2.131 1.85-1.058 4.451-0.816 5.51 1.034 0.134 0.202 0.268 0.403 0.254 0.644 0.415 1.234 0.241 2.628 0.12 3.928-0.551 3.794-1.236 7.387-2.029 11.168-0.591 2.775-1.275 5.496-2.764 7.882-0.108 0.188-0.309 0.322-0.496 0.214 0.442 0.751 0.106 1.555-0.417 2.252-0.429 0.751-1.046 1.394-1.569 2.091-0.47 0.604-0.939 1.207-1.409 1.81-2.239 2.828-4.573 5.602-7.135 8.122 1.556-0.978 3.031-2.251 4.184-3.833 0.886-1.112 1.583-2.332 2.509-3.297 0.979-1.059 2.226-1.715 3.205-2.774 0.416-0.51 0.885-1.113 1.395-1.568 0.066 1.407 0.039 2.761 0.106 4.169 0.201-1.005 0.403-2.011 0.604-3.016 0.215-1.247 0.524-2.44 0.832-3.633 0.94-2.949 2.885-5.697 3.395-8.767 0.014-0.241 0.027-0.483 0.041-0.724-0.201 0.134-0.309 0.322-0.51 0.456-0.04-0.148 0.067-0.336 0.027-0.483 0.055-2.708 0.646-5.483 1.371-8.057 0.563-2.293 1.235-4.773 2.965-6.274 0.509-0.455 1.113-0.857 1.622-1.313 0.51-0.456 0.845-1.26 0.738-1.944 3.178 0.323 6.66 1.265 9.329-0.571 2.065-1.434 4.621-0.818 5.075-2.713-0.792 0.735 1.103 1.189 2.659 0.483s23.915-14.808 24.838-26.835c-0.911-1.8908-2.519-3.4332-4.316-4.2114-0.094-0.0537-0.094-0.0537-0.187-0.1073 2.923-1.1923 5.993-2.4246 8.796-4.0593 0.805-0.536 1.703-1.0184 2.28-1.8092 0.671-1.6086 0.832-3.633 0.94-5.5635z" fill="#5c5a5a" style=""/>
<path d="m221.07 127.62c-1.005-0.202-1.526-0.498-2.558-0.217-2.065 0.562-2.591 1.325-4.575 2.183-2.226 0.844-3.364 0.839-5.79 0.946-1.261 0.026-2.467-0.041-3.62-0.203 0.187 0.979-1.8 2.31-2.564 2.993-0.764 0.684-1.717 1.26-2.106 2.158-0.442 0.992-0.309 2.065-0.416 3.124-0.162 1.153-0.658 2.238-1.061 3.378-0.711 2.332-0.605 4.759-0.351 7.146 0.054-0.094 0.054-0.094 0.108-0.188 0.054-2.708 0.645-5.483 1.37-8.057 0.564-2.293 1.235-4.773 2.965-6.274 0.51-0.455 1.113-0.857 1.623-1.313 0.509-0.456 2.054-1.666 1.946-2.349 3.178 0.322 5.388 1.065 8.056-0.771 2.066-1.434 2.736-1.244 5.15-1.98 0.402-0.268 1.14-0.469 1.823-0.576z" fill="#8b8b8b" style=""/> <path d="m221.07 127.62c-1.005-0.202-1.526-0.498-2.558-0.217-2.065 0.562-2.591 1.325-4.575 2.183-2.226 0.844-3.364 0.839-5.79 0.946-1.261 0.026-2.467-0.041-3.62-0.203 0.187 0.979-1.8 2.31-2.564 2.993-0.764 0.684-1.717 1.26-2.106 2.158-0.442 0.992-0.309 2.065-0.416 3.124-0.162 1.153-0.658 2.238-1.061 3.378-0.711 2.332-0.605 4.759-0.351 7.146 0.054-0.094 0.054-0.094 0.108-0.188 0.054-2.708 0.645-5.483 1.37-8.057 0.564-2.293 1.235-4.773 2.965-6.274 0.51-0.455 1.113-0.857 1.623-1.313 0.509-0.456 2.054-1.666 1.946-2.349 3.178 0.322 5.388 1.065 8.056-0.771 2.066-1.434 2.736-1.244 5.15-1.98 0.402-0.268 1.14-0.469 1.823-0.576z" fill="#8b8b8b" style=""/>

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 77 KiB

11
application/src/main/data/json/system/scada_symbols/vertical-pipe.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Vertical pipe", "title": "Vertical pipe",
"description": "Vertical pipe with fluid and leak visualizations.", "description": "Vertical pipe with fluid and leak visualizations.",
"searchTags": [ "searchTags": [
@ -11,7 +10,7 @@
"tags": [ "tags": [
{ {
"tag": "fluid", "tag": "fluid",
"stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill');\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(ctx.values.flowAnimationSpeed);\n }\n} else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}", "stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill').first();\n\nvar fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(ctx.values.flowAnimationSpeed);\n }\n }\n} else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}",
"actions": null "actions": null
}, },
{ {
@ -240,7 +239,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><path d="m64 186v-172h72v172h-72z" fill="#fff" tb:tag="pipe-background"/><path d="m64 186v-172h72v172h-72z" fill="url(#paint0_linear_1245_55957)"/><g stroke-width="3"> }]]></tb:metadata>
<path d="m64 186v-172h72v172h-72z" fill="#fff" tb:tag="pipe-background"/><path d="m64 186v-172h72v172h-72z" fill="url(#paint0_linear_1245_55957)"/><g stroke-width="3">
<path d="m65.5 184.5v-169h69v169h-69z" stroke="#000" stroke-opacity=".12"/> <path d="m65.5 184.5v-169h69v169h-69z" stroke="#000" stroke-opacity=".12"/>
<rect transform="rotate(-90 51.5 198.5)" x="51.5" y="198.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171"/> <rect transform="rotate(-90 51.5 198.5)" x="51.5" y="198.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171"/>
<rect transform="rotate(-90 51.5 12.5)" x="51.5" y="12.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171"/> <rect transform="rotate(-90 51.5 12.5)" x="51.5" y="12.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171"/>
@ -254,7 +254,7 @@
<stop stop-color="#727171" stop-opacity=".35" offset=".71855"/> <stop stop-color="#727171" stop-opacity=".35" offset=".71855"/>
<stop stop-color="#727171" offset="1"/> <stop stop-color="#727171" offset="1"/>
</linearGradient> </linearGradient>
<pattern id="liquid" width="172" height="72" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse"> <pattern id="baseLiquid" width="172" height="72" patternTransform="translate(14,8)" patternUnits="userSpaceOnUse">
<circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/> <circle transform="rotate(-90)" cx="-15" cy="21" r="8" fill="url(#paint28_linear_1182_32781-5)"/>
<circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/> <circle transform="rotate(-90)" cx="-15" cy="150" r="8" fill="url(#paint29_linear_1182_32781-1)"/>
<circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/> <circle transform="rotate(-90)" cx="-16" cy="113" r="8" fill="url(#paint30_linear_1182_32781-7)"/>
@ -285,6 +285,7 @@
<circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/> <circle transform="rotate(-90)" cx="-58.5" cy="95.5" r="9.5" fill="url(#paint55_linear_1182_32781-8)"/>
<path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/> <path d="m0 0h172v72h-172z" fill="url(#paint84_linear_1182_32781-8)" stroke-width=".57735"/>
</pattern> </pattern>
<pattern id="liquid" width="172" height="72" patternUnits="userSpaceOnUse"><rect width="688" height="72" x="-172" y="0" stroke-width="0" fill="url(#baseLiquid)"/></pattern>
<linearGradient id="paint28_linear_1182_32781-5" x1="19.316" x2="21" y1="8.2632" y2="23" gradientTransform="translate(-36,6)" gradientUnits="userSpaceOnUse"> <linearGradient id="paint28_linear_1182_32781-5" x1="19.316" x2="21" y1="8.2632" y2="23" gradientTransform="translate(-36,6)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/> <stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".15" offset="1"/> <stop stop-opacity=".15" offset="1"/>

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

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

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="800" height="600" fill="none" version="1.1" viewBox="0 0 800 600"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="800" height="600" fill="none" version="1.1" viewBox="0 0 800 600"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Vertical short tank", "title": "Vertical short tank",
"description": "Vertical short tank with current volume value and level visualizations.", "description": "Vertical short tank with current volume value and level visualizations.",
"searchTags": [ "searchTags": [
@ -25,17 +24,17 @@
}, },
{ {
"tag": "fluid", "tag": "fluid",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 245});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*442; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 245 + height });\n }\n}\n", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 245});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*442; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 245 + height });\n }\n}\n",
"actions": null "actions": null
}, },
{ {
"tag": "fluid-background", "tag": "fluid-background",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*442; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*442; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n",
"actions": null "actions": null
}, },
{ {
"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', 'dominant-baseline': 'middle', class: 'majorTickText'});\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.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(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 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}",
"actions": null "actions": null
}, },
{ {
@ -561,7 +560,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><path d="m1 101.5c0-100.5 269.05-100.5 269.05-100.5h266.67s264.29 0 264.29 100.5v487.71c0 6.628-5.373 11.793-12 11.793h-776c-6.6274 0-12-5.165-12-11.793v-487.71z" fill="#E5E5E5" tb:tag="background"/><path d="m1 101.5c0-100.5 269.05-100.5 269.05-100.5h266.67s264.29 0 264.29 100.5v487.71c0 6.628-5.373 11.793-12 11.793h-776c-6.6274 0-12-5.165-12-11.793v-487.71z" fill="url(#paint0_linear_1693_189770)"/><path d="m1 125h800v464c0 6.627-5.373 12-12 12h-776c-6.6274 0-12-5.373-12-12v-464z" fill="#4A4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="9" y="-579" width="784" height="200" fill="#1ec1f4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="9" y="-579" width="784" height="200" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-199-inside-1_1693_189770" fill="white"> }]]></tb:metadata>
<path d="m1 101.5c0-100.5 269.05-100.5 269.05-100.5h266.67s264.29 0 264.29 100.5v487.71c0 6.628-5.373 11.793-12 11.793h-776c-6.6274 0-12-5.165-12-11.793v-487.71z" fill="#E5E5E5" tb:tag="background"/><path d="m1 101.5c0-100.5 269.05-100.5 269.05-100.5h266.67s264.29 0 264.29 100.5v487.71c0 6.628-5.373 11.793-12 11.793h-776c-6.6274 0-12-5.165-12-11.793v-487.71z" fill="url(#paint0_linear_1693_189770)"/><path d="m1 125h800v464c0 6.627-5.373 12-12 12h-776c-6.6274 0-12-5.373-12-12v-464z" fill="#4A4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="9" y="-579" width="784" height="200" fill="#1ec1f4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="9" y="-579" width="784" height="200" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-199-inside-1_1693_189770" fill="white">
<path d="m269.05 0s-269.05 0-269.05 100.5v487.71c0 6.628 5.3726 11.793 12 11.793h776c6.627 0 12-5.165 12-11.793v-487.71c0-100.5-264.29-100.5-264.29-100.5h-266.67zm316.95 135c-3.866 0-7 3.134-7 7v428c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-428c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill-rule="evenodd"/> <path d="m269.05 0s-269.05 0-269.05 100.5v487.71c0 6.628 5.3726 11.793 12 11.793h776c6.627 0 12-5.165 12-11.793v-487.71c0-100.5-264.29-100.5-264.29-100.5h-266.67zm316.95 135c-3.866 0-7 3.134-7 7v428c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-428c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill-rule="evenodd"/>
</mask><g tb:tag="top-layer"> </mask><g tb:tag="top-layer">
<path d="m269.05 0s-269.05 0-269.05 100.5v487.71c0 6.628 5.3726 11.793 12 11.793h776c6.627 0 12-5.165 12-11.793v-487.71c0-100.5-264.29-100.5-264.29-100.5h-266.67zm316.95 135c-3.866 0-7 3.134-7 7v428c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-428c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill="#E5E5E5" fill-rule="evenodd" tb:tag="background"/> <path d="m269.05 0s-269.05 0-269.05 100.5v487.71c0 6.628 5.3726 11.793 12 11.793h776c6.627 0 12-5.165 12-11.793v-487.71c0-100.5-264.29-100.5-264.29-100.5h-266.67zm316.95 135c-3.866 0-7 3.134-7 7v428c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-428c0-3.866-3.134-7-7-7h-44z" clip-rule="evenodd" fill="#E5E5E5" fill-rule="evenodd" tb:tag="background"/>
@ -606,7 +606,7 @@
</mask><path d="m8 125c-3.866 0-7-3.134-7-7s3.134-7 7-7h786c3.866 0 7 3.134 7 7s-3.134 7-7 7h-786z" fill="#D9D9D9"/><path d="m8 114h786v-6h-786v6zm786 8h-786v6h786v-6zm4-4c0 2.209-1.791 4-4 4v6c5.523 0 10-4.477 10-10h-6zm-4-4c2.209 0 4 1.791 4 4h6c0-5.523-4.477-10-10-10v6zm-790 4c0-2.209 1.7909-4 4-4v-6c-5.5228 0-10 4.477-10 10h6zm-6 0c0 5.523 4.4772 10 10 10v-6c-2.2092 0-4-1.791-4-4h-6z" fill="#727171" mask="url(#path-234-inside-2_1693_189770)"/><g filter="url(#filter0_ii_1693_189770)" tb:tag="value-box"> </mask><path d="m8 125c-3.866 0-7-3.134-7-7s3.134-7 7-7h786c3.866 0 7 3.134 7 7s-3.134 7-7 7h-786z" fill="#D9D9D9"/><path d="m8 114h786v-6h-786v6zm786 8h-786v6h786v-6zm4-4c0 2.209-1.791 4-4 4v6c5.523 0 10-4.477 10-10h-6zm-4-4c2.209 0 4 1.791 4 4h6c0-5.523-4.477-10-10-10v6zm-790 4c0-2.209 1.7909-4 4-4v-6c-5.5228 0-10 4.477-10 10h6zm-6 0c0 5.523 4.4772 10 10 10v-6c-2.2092 0-4-1.791-4-4h-6z" fill="#727171" mask="url(#path-234-inside-2_1693_189770)"/><g filter="url(#filter0_ii_1693_189770)" tb:tag="value-box">
<path d="m281 31c0-6.6274 5.373-12 12-12h216c6.627 0 12 5.3726 12 12v56c0 6.6274-5.373 12-12 12h-216c-6.627 0-12-5.3726-12-12z" fill="#f3f3f3" tb:tag="value-box-background"/> <path d="m281 31c0-6.6274 5.373-12 12-12h216c6.627 0 12 5.3726 12 12v56c0 6.6274-5.373 12-12 12h-216c-6.627 0-12-5.3726-12-12z" fill="#f3f3f3" tb:tag="value-box-background"/>
<path d="m282.5 31c0-5.799 4.701-10.5 10.5-10.5h216c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-216c-5.799 0-10.5-4.701-10.5-10.5z" stroke="#727171" stroke-width="3"/> <path d="m282.5 31c0-5.799 4.701-10.5 10.5-10.5h216c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-216c-5.799 0-10.5-4.701-10.5-10.5z" stroke="#727171" stroke-width="3"/>
<text x="399.12082" y="62.601822" dominant-baseline="middle" fill="#727171" font-family="Roboto" font-size="32px" font-weight="500" text-anchor="middle" tb:tag="value-text" xml:space="preserve"><tspan>1660 gal</tspan></text> <text x="399.12082" y="62.601822" fill="#727171" font-family="Roboto" font-size="32px" font-weight="500" tb:tag="value-text" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">1660 gal</tspan></text>
</g><defs> </g><defs>
<filter id="filter0_ii_1693_189770" x="277" y="15" width="248" height="88" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"> <filter id="filter0_ii_1693_189770" x="277" y="15" width="248" height="88" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
<feFlood flood-opacity="0" result="BackgroundImageFix"/> <feFlood flood-opacity="0" result="BackgroundImageFix"/>
@ -642,7 +642,7 @@
<stop stop-color="#020202" stop-opacity=".12" offset=".76303"/> <stop stop-color="#020202" stop-opacity=".12" offset=".76303"/>
<stop stop-color="#020202" stop-opacity=".35" offset="1"/> <stop stop-color="#020202" stop-opacity=".35" offset="1"/>
</linearGradient> </linearGradient>
<pattern id="liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid"> <pattern id="base-liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/> <circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/>
<circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/> <circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/>
<circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/> <circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/>
@ -790,6 +790,7 @@
<circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/> <circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/>
<path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/> <path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/>
</pattern> </pattern>
<pattern id="liquid" width="584" height="201" patternUnits="userSpaceOnUse"><rect width="584" height="201" x="0" y="0" stroke-width="0" fill="url(#base-liquid)"/></pattern>
<linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse"> <linearGradient id="paint1_linear_1687_130892" x1="99.316" x2="101" y1="817.26" y2="832" gradientTransform="translate(-8,-791)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".4" offset="0"/> <stop stop-color="#fff" stop-opacity=".4" offset="0"/>
<stop stop-opacity=".05" offset="1"/> <stop stop-opacity=".05" offset="1"/>

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

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

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="600" height="1e3" fill="none" version="1.1" viewBox="0 0 600 1e3"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="600" height="1e3" fill="none" version="1.1" viewBox="0 0 600 1e3"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Vertical tank", "title": "Vertical tank",
"description": "Vertical tank with current volume value and level visualizations.", "description": "Vertical tank with current volume value and level visualizations.",
"searchTags": [ "searchTags": [
@ -24,17 +23,17 @@
}, },
{ {
"tag": "fluid", "tag": "fluid",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*765; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*765; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n",
"actions": null "actions": null
}, },
{ {
"tag": "fluid-background", "tag": "fluid-background",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*765; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n", "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*765; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n",
"actions": null "actions": null
}, },
{ {
"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', 'dominant-baseline': 'middle', class: 'majorTickText'});\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.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(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 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}",
"actions": null "actions": null
}, },
{ {
@ -560,7 +559,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><path d="m0 167.5c0-167.5 201.79-167.5 201.79-167.5h200s198.21 0 198.21 167.5v820.9c0 6.628-5.373 11.601-12 11.601h-576c-6.6274 0-12-4.973-12-11.601v-820.9z" fill="#e5e5e5" tb:tag="background"/><path d="m0 167.5c0-167.5 201.79-167.5 201.79-167.5h200s198.21 0 198.21 167.5v820.9c0 6.628-5.373 11.601-12 11.601h-576c-6.6274 0-12-4.973-12-11.601v-820.9z" fill="url(#paint0_linear_1687_130892)" style="fill:url(#paint0_linear_1687_130892)"/><path d="m0 181h600v807c0 6.627-5.373 12-12 12h-576c-6.6274 0-12-5.373-12-12z" fill="#4a4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="8" y="-968" width="584" height="201" fill="#1ec1f4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="8" y="-968" width="584" height="201" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-150-inside-1_1687_130892" fill="#ffffff"> }]]></tb:metadata>
<path d="m0 167.5c0-167.5 201.79-167.5 201.79-167.5h200s198.21 0 198.21 167.5v820.9c0 6.628-5.373 11.601-12 11.601h-576c-6.6274 0-12-4.973-12-11.601v-820.9z" fill="#e5e5e5" tb:tag="background"/><path d="m0 167.5c0-167.5 201.79-167.5 201.79-167.5h200s198.21 0 198.21 167.5v820.9c0 6.628-5.373 11.601-12 11.601h-576c-6.6274 0-12-4.973-12-11.601v-820.9z" fill="url(#paint0_linear_1687_130892)" style="fill:url(#paint0_linear_1687_130892)"/><path d="m0 181h600v807c0 6.627-5.373 12-12 12h-576c-6.6274 0-12-5.373-12-12z" fill="#4a4848" fill-opacity=".5" tb:tag="scale-background"/><rect transform="scale(1,-1)" x="8" y="-968" width="584" height="201" fill="#1ec1f4" fill-opacity=".5" tb:tag="fluid-background"/><rect transform="scale(1,-1)" x="8" y="-968" width="584" height="201" fill="url(#liquid)" tb:tag="fluid"/><mask id="path-150-inside-1_1687_130892" fill="#ffffff">
<path d="m201.79 0s-201.79 0-201.79 167.5v820.9c0 6.628 5.3726 11.601 12 11.601h576c6.627 0 12-4.973 12-11.601v-820.9c0-167.5-198.21-167.5-198.21-167.5h-101.79zm201.21 203c-3.866 0-7 3.134-7 7v751c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-751c0-3.866-3.134-7-7-7z" clip-rule="evenodd" fill-rule="evenodd"/> <path d="m201.79 0s-201.79 0-201.79 167.5v820.9c0 6.628 5.3726 11.601 12 11.601h576c6.627 0 12-4.973 12-11.601v-820.9c0-167.5-198.21-167.5-198.21-167.5h-101.79zm201.21 203c-3.866 0-7 3.134-7 7v751c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-751c0-3.866-3.134-7-7-7z" clip-rule="evenodd" fill-rule="evenodd"/>
</mask><g tb:tag="top-layer"> </mask><g tb:tag="top-layer">
<path d="m201.79 0s-201.79 0-201.79 167.5v820.9c0 6.628 5.3726 11.601 12 11.601h576c6.627 0 12-4.973 12-11.601v-820.9c0-167.5-198.21-167.5-198.21-167.5h-101.79zm201.21 203c-3.866 0-7 3.134-7 7v751c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-751c0-3.866-3.134-7-7-7z" clip-rule="evenodd" fill="#e5e5e5" fill-rule="evenodd" tb:tag="background"/> <path d="m201.79 0s-201.79 0-201.79 167.5v820.9c0 6.628 5.3726 11.601 12 11.601h576c6.627 0 12-4.973 12-11.601v-820.9c0-167.5-198.21-167.5-198.21-167.5h-101.79zm201.21 203c-3.866 0-7 3.134-7 7v751c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-751c0-3.866-3.134-7-7-7z" clip-rule="evenodd" fill="#e5e5e5" fill-rule="evenodd" tb:tag="background"/>
@ -635,7 +635,7 @@
</mask><path d="m7 181c-3.866 0-7-3.134-7-7s3.134-7 7-7h586c3.866 0 7 3.134 7 7s-3.134 7-7 7z" fill="#d9d9d9"/><path d="m7 170h586v-6h-586zm586 8h-586v6h586zm4-4c0 2.209-1.791 4-4 4v6c5.523 0 10-4.477 10-10zm-4-4c2.209 0 4 1.791 4 4h6c0-5.523-4.477-10-10-10zm-590 4c0-2.209 1.7909-4 4-4v-6c-5.5228 0-10 4.477-10 10zm-6 0c0 5.523 4.4771 10 10 10v-6c-2.2091 0-4-1.791-4-4z" fill="#727171" mask="url(#path-215-inside-2_1687_130892)"/><g filter="url(#filter0_ii_1687_130892)" tb:tag="value-box"> </mask><path d="m7 181c-3.866 0-7-3.134-7-7s3.134-7 7-7h586c3.866 0 7 3.134 7 7s-3.134 7-7 7z" fill="#d9d9d9"/><path d="m7 170h586v-6h-586zm586 8h-586v6h586zm4-4c0 2.209-1.791 4-4 4v6c5.523 0 10-4.477 10-10zm-4-4c2.209 0 4 1.791 4 4h6c0-5.523-4.477-10-10-10zm-590 4c0-2.209 1.7909-4 4-4v-6c-5.5228 0-10 4.477-10 10zm-6 0c0 5.523 4.4771 10 10 10v-6c-2.2091 0-4-1.791-4-4z" fill="#727171" mask="url(#path-215-inside-2_1687_130892)"/><g filter="url(#filter0_ii_1687_130892)" tb:tag="value-box">
<path d="m180 62c0-6.6274 5.373-12 12-12h216c6.627 0 12 5.3726 12 12v56c0 6.627-5.373 12-12 12h-216c-6.627 0-12-5.373-12-12z" fill="#f3f3f3" tb:tag="value-box-background"/> <path d="m180 62c0-6.6274 5.373-12 12-12h216c6.627 0 12 5.3726 12 12v56c0 6.627-5.373 12-12 12h-216c-6.627 0-12-5.373-12-12z" fill="#f3f3f3" tb:tag="value-box-background"/>
<path d="m192 51.5h216c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-216c-5.799 0-10.5-4.701-10.5-10.5v-56c0-5.799 4.701-10.5 10.5-10.5z" stroke="#727171" stroke-width="3"/> <path d="m192 51.5h216c5.799 0 10.5 4.701 10.5 10.5v56c0 5.799-4.701 10.5-10.5 10.5h-216c-5.799 0-10.5-4.701-10.5-10.5v-56c0-5.799 4.701-10.5 10.5-10.5z" stroke="#727171" stroke-width="3"/>
<text x="301.5625" y="93.546875" dominant-baseline="middle" fill="#727171" font-family="Roboto" font-size="32px" font-weight="500" text-anchor="middle" tb:tag="value-text" xml:space="preserve"><tspan>1660 gal</tspan></text> <text x="301.5625" y="93.546875" fill="#727171" font-family="Roboto" font-size="32px" font-weight="500" tb:tag="value-text" xml:space="preserve" text-anchor="middle"><tspan dominant-baseline="middle">1660 gal</tspan></text>
</g><defs> </g><defs>
<filter id="filter0_ii_1687_130892" x="176" y="46" width="248" height="88" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"> <filter id="filter0_ii_1687_130892" x="176" y="46" width="248" height="88" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
<feFlood flood-opacity="0" result="BackgroundImageFix"/> <feFlood flood-opacity="0" result="BackgroundImageFix"/>
@ -1242,7 +1242,7 @@
<stop stop-color="#fff" stop-opacity=".25" offset="0"/> <stop stop-color="#fff" stop-opacity=".25" offset="0"/>
<stop stop-opacity=".1" offset="1"/> <stop stop-opacity=".1" offset="1"/>
</linearGradient> </linearGradient>
<pattern id="liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid"> <pattern id="base-liquid" width="584" height="201" patternTransform="translate(8,791)" patternUnits="userSpaceOnUse" preserveAspectRatio="xMidYMid">
<circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/> <circle cx="93" cy="33" r="8" fill="url(#paint1_linear_1687_130892)" style="fill:url(#paint1_linear_1687_130892)"/>
<circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/> <circle cx="67" cy="166" r="8" fill="url(#paint2_linear_1687_130892)" style="fill:url(#paint2_linear_1687_130892)"/>
<circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/> <circle cx="27" cy="193" r="8" fill="url(#paint3_linear_1687_130892)" style="fill:url(#paint3_linear_1687_130892)"/>
@ -1390,5 +1390,6 @@
<circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/> <circle cx="376" cy="5" r="5" fill="url(#paint145_linear_1687_130892)" style="fill:url(#paint145_linear_1687_130892)"/>
<path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/> <path d="m0 0h584v197c0 2.209-1.791 4-4 4h-576c-2.2092 0-4-1.791-4-4z" fill="#1EC1F4" fill-opacity=".5" style="fill-opacity:0"/>
</pattern> </pattern>
<pattern id="liquid" width="584" height="201" patternUnits="userSpaceOnUse"><rect width="584" height="201" x="0" y="0" stroke-width="0" fill="url(#base-liquid)"/></pattern>
</defs><path d="m201.79-5e-4s-201.79 0-201.79 167.5v820.9c0 6.628 5.3726 11.601 12 11.601h576c6.627 0 12-4.973 12-11.601v-820.9c0-167.5-198.21-167.5-198.21-167.5h-101.79zm201.21 203c-3.866 0-7 3.134-7 7v751c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-751c0-3.866-3.134-7-7-7z" fill="#000" fill-opacity="0" tb:tag="clickArea"/> </defs><path d="m201.79-5e-4s-201.79 0-201.79 167.5v820.9c0 6.628 5.3726 11.601 12 11.601h576c6.627 0 12-4.973 12-11.601v-820.9c0-167.5-198.21-167.5-198.21-167.5h-101.79zm201.21 203c-3.866 0-7 3.134-7 7v751c0 3.866 3.134 7 7 7h44c3.866 0 7-3.134 7-7v-751c0-3.866-3.134-7-7-7z" fill="#000" fill-opacity="0" tb:tag="clickArea"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

4
application/src/main/data/json/system/scada_symbols/vertical-wheel-valve.svg

@ -10,7 +10,7 @@
"tags": [ "tags": [
{ {
"tag": "background", "tag": "background",
"stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar color = opened ? ctx.properties.openedColor : ctx.properties.closedColor;\nif (!openAnimate) {\n element.attr({fill: color});\n} else {\n ctx.api.animate(element, 500).attr({fill: color});\n element.remember('openAnimate', false);\n}\n", "stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar color = opened ? ctx.properties.openedColor : ctx.properties.closedColor;\nif (!openAnimate) {\n element.attr({fill: color});\n} else {\n ctx.api.cssAnimate(element, 500).attr({fill: color});\n element.remember('openAnimate', false);\n}\n",
"actions": null "actions": null
}, },
{ {
@ -24,7 +24,7 @@
}, },
{ {
"tag": "wheel", "tag": "wheel",
"stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar angle = opened ? ctx.properties.openedRotationAngle : ctx.properties.closedRotationAngle;\nif (!openAnimate) {\n element.transform({rotate: angle});\n} else {\n ctx.api.animate(element, 500).transform({rotate: angle});\n element.remember('openAnimate', false);\n}\n", "stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar angle = opened ? ctx.properties.openedRotationAngle : ctx.properties.closedRotationAngle;\nif (!openAnimate) {\n element.transform({rotate: angle});\n} else {\n ctx.api.cssAnimate(element, 500).transform({rotate: angle});\n element.remember('openAnimate', false);\n}\n",
"actions": null "actions": null
} }
], ],

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

12
application/src/main/data/json/system/scada_symbols/waterstop.svg

@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{
<tb:metadata xmlns=""><![CDATA[{
"title": "Water stop", "title": "Water stop",
"description": "Remotely controlled water shutoff valve with configurable connectors and various states.", "description": "Remotely controlled water shutoff valve with configurable connectors and various states.",
"searchTags": [ "searchTags": [
@ -25,7 +24,7 @@
}, },
{ {
"tag": "walve", "tag": "walve",
"stateRenderFunction": "var opened = ctx.values.opened;\nif (opened) {\n element.attr({fill: ctx.properties.openedColor});\n ctx.api.resetAnimation(element);\n} else {\n element.attr({fill: ctx.properties.closedColor});\n if (ctx.values.closeAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}\n", "stateRenderFunction": "var opened = ctx.values.opened;\nif (opened) {\n element.attr({fill: ctx.properties.openedColor});\n ctx.api.resetCssAnimation(element);\n} else {\n element.attr({fill: ctx.properties.closedColor});\n if (ctx.values.closeAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n",
"actions": null "actions": null
} }
], ],
@ -93,7 +92,7 @@
}, },
"valueToData": { "valueToData": {
"type": "CONSTANT", "type": "CONSTANT",
"constantValue": false, "constantValue": true,
"valueToDataFunction": "/* Convert input boolean value to RPC parameters or attribute/time-series value */\nreturn value;" "valueToDataFunction": "/* Convert input boolean value to RPC parameters or attribute/time-series value */\nreturn value;"
} }
}, },
@ -127,7 +126,7 @@
}, },
"valueToData": { "valueToData": {
"type": "CONSTANT", "type": "CONSTANT",
"constantValue": true, "constantValue": false,
"valueToDataFunction": "/* Convert input boolean value to RPC parameters or attribute/time-series value */\nreturn value;" "valueToDataFunction": "/* Convert input boolean value to RPC parameters or attribute/time-series value */\nreturn value;"
} }
}, },
@ -219,7 +218,8 @@
"step": null "step": null
} }
] ]
}]]></tb:metadata><path d="M14 64H186V136H14V64Z" fill="#fff" tb:tag="pipe-color"/><path d="M14 64H186V136H14V64Z" fill="url(#paint0_linear_2089_217683)"/><path d="m15.5 65.5h169v69h-169v-69z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="187.5" y="51.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171" stroke-width="3"/><rect x="1.5" y="51.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171" stroke-width="3"/><path d="m70.5 46c0 1.933 1.567 3.5 3.5 3.5l52 1e-4c1.933 0 3.5-1.567 3.5-3.5v-37c0-4.6944-3.806-8.5-8.5-8.5h-42c-4.6944 0-8.5 3.8056-8.5 8.5v37z" fill="#1C943E" tb:tag="walve"/><path d="m70.5 46c0 1.933 1.567 3.5 3.5 3.5l52 1e-4c1.933 0 3.5-1.567 3.5-3.5v-37c0-4.6944-3.806-8.5-8.5-8.5h-42c-4.6944 0-8.5 3.8056-8.5 8.5v37z" fill="url(#paint1_linear_2089_217683)"/><path d="m70.5 46c0 1.933 1.567 3.5 3.5 3.5l52 1e-4c1.933 0 3.5-1.567 3.5-3.5v-37c0-4.6944-3.806-8.5-8.5-8.5h-42c-4.6944 0-8.5 3.8056-8.5 8.5v37z" stroke="#fff"/><path d="m110 136v-72h-21v72h21z" fill="#1C943E" tb:tag="walve"/><path d="m110 136v-72h-21v72h21z" fill="url(#paint2_linear_2089_217683)"/><path d="m110 136v-72h-21v72h21z" fill="url(#paint3_linear_2089_217683)"/><path d="m109.5 135.5v-71h-20v71h20z" stroke="#000" stroke-opacity=".12"/><path d="m126 64c2.209 0 4-1.7908 4-3.9999v-6c0-2.2092-1.791-4.0001-4-4.0001h-52c-2.2091 0-4 1.7909-4 4v6c0 2.2091 1.7909 4 4 4h52z" fill="#647484"/><path d="m126 64c2.209 0 4-1.7908 4-3.9999v-6c0-2.2092-1.791-4.0001-4-4.0001h-52c-2.2091 0-4 1.7909-4 4v6c0 2.2091 1.7909 4 4 4h52z" fill="url(#paint4_linear_2089_217683)"/><path d="m126 62.5c1.381 0 2.5-1.1193 2.5-2.5v-6c0-1.3807-1.119-2.5-2.5-2.5h-52c-1.3807 0-2.5 1.1193-2.5 2.5v6c0 1.3807 1.1193 2.5 2.5 2.5h52z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m67.262 0s-67.262 0-67.262 33.5v164.18c0 1.3256 1.7909 2.3202 4 2.3202h192c2.209 0 4-0.9946 4-2.3202v-164.18c0-33.5-66.07-33.5-66.07-33.5h-33.93zm67.07 40.6c-1.2887 0-2.3332 0.6268-2.3332 1.4v150.2c0 0.7732 1.0447 1.4 2.3332 1.4h14.667c1.2887 0 2.3332-0.6268 2.3332-1.4v-150.2c0-0.7732-1.0447-1.4-2.3332-1.4z" fill="#000" fill-opacity="0" tb:tag="clickArea"/><defs> }]]></tb:metadata>
<path d="M14 64H186V136H14V64Z" fill="#fff" tb:tag="pipe-color"/><path d="M14 64H186V136H14V64Z" fill="url(#paint0_linear_2089_217683)"/><path d="m15.5 65.5h169v69h-169v-69z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="187.5" y="51.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171" stroke-width="3"/><rect x="1.5" y="51.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171" stroke-width="3"/><path d="m70.5 46c0 1.933 1.567 3.5 3.5 3.5l52 1e-4c1.933 0 3.5-1.567 3.5-3.5v-37c0-4.6944-3.806-8.5-8.5-8.5h-42c-4.6944 0-8.5 3.8056-8.5 8.5v37z" fill="#1C943E" tb:tag="walve"/><path d="m70.5 46c0 1.933 1.567 3.5 3.5 3.5l52 1e-4c1.933 0 3.5-1.567 3.5-3.5v-37c0-4.6944-3.806-8.5-8.5-8.5h-42c-4.6944 0-8.5 3.8056-8.5 8.5v37z" fill="url(#paint1_linear_2089_217683)"/><path d="m70.5 46c0 1.933 1.567 3.5 3.5 3.5l52 1e-4c1.933 0 3.5-1.567 3.5-3.5v-37c0-4.6944-3.806-8.5-8.5-8.5h-42c-4.6944 0-8.5 3.8056-8.5 8.5v37z" stroke="#fff"/><path d="m110 136v-72h-21v72h21z" fill="#1C943E" tb:tag="walve"/><path d="m110 136v-72h-21v72h21z" fill="url(#paint2_linear_2089_217683)"/><path d="m110 136v-72h-21v72h21z" fill="url(#paint3_linear_2089_217683)"/><path d="m109.5 135.5v-71h-20v71h20z" stroke="#000" stroke-opacity=".12"/><path d="m126 64c2.209 0 4-1.7908 4-3.9999v-6c0-2.2092-1.791-4.0001-4-4.0001h-52c-2.2091 0-4 1.7909-4 4v6c0 2.2091 1.7909 4 4 4h52z" fill="#647484"/><path d="m126 64c2.209 0 4-1.7908 4-3.9999v-6c0-2.2092-1.791-4.0001-4-4.0001h-52c-2.2091 0-4 1.7909-4 4v6c0 2.2091 1.7909 4 4 4h52z" fill="url(#paint4_linear_2089_217683)"/><path d="m126 62.5c1.381 0 2.5-1.1193 2.5-2.5v-6c0-1.3807-1.119-2.5-2.5-2.5h-52c-1.3807 0-2.5 1.1193-2.5 2.5v6c0 1.3807 1.1193 2.5 2.5 2.5h52z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m67.262 0s-67.262 0-67.262 33.5v164.18c0 1.3256 1.7909 2.3202 4 2.3202h192c2.209 0 4-0.9946 4-2.3202v-164.18c0-33.5-66.07-33.5-66.07-33.5h-33.93zm67.07 40.6c-1.2887 0-2.3332 0.6268-2.3332 1.4v150.2c0 0.7732 1.0447 1.4 2.3332 1.4h14.667c1.2887 0 2.3332-0.6268 2.3332-1.4v-150.2c0-0.7732-1.0447-1.4-2.3332-1.4z" fill="#000" fill-opacity="0" tb:tag="clickArea"/><defs>
<linearGradient id="paint0_linear_2089_217683" x1="58.72" x2="58.53" y1="64" y2="136" gradientUnits="userSpaceOnUse"> <linearGradient id="paint0_linear_2089_217683" x1="58.72" x2="58.53" y1="64" y2="136" gradientUnits="userSpaceOnUse">
<stop stop-color="#727171" offset="0"/> <stop stop-color="#727171" offset="0"/>
<stop stop-color="#727171" stop-opacity=".35" offset=".26388"/> <stop stop-color="#727171" stop-opacity=".35" offset=".26388"/>

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

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

@ -11,7 +11,7 @@
"resources": [], "resources": [],
"templateHtml": "<tb-gateway-connector [device]=\"entityId\" *ngIf=\"entityId\" [ctx]=\"ctx\"></tb-gateway-connector>", "templateHtml": "<tb-gateway-connector [device]=\"entityId\" *ngIf=\"entityId\" [ctx]=\"ctx\"></tb-gateway-connector>",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n if (self.ctx.datasources && self.ctx.datasources.length) {\n self.ctx.$scope.entityId = self.ctx.datasources[0].entity.id;\n }\n};\n\nself.typeParameters = function() {\n return {\n dataKeysOptional: true,\n singleEntity: true\n };\n}", "controllerScript": "self.onInit = function() {\n if (self.ctx.datasources && self.ctx.datasources.length) {\n self.ctx.$scope.entityId = self.ctx.datasources[0].entity.id;\n }\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.gatewayConnectors?.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n dataKeysOptional: true,\n singleEntity: true\n };\n}",
"settingsSchema": "{}", "settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Gateway connectors\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":false,\"enableDataExport\":false,\"widgetStyle\":{},\"widgetCss\":\"\",\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":500},\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showLegend\":false}" "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Gateway connectors\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":false,\"enableDataExport\":false,\"widgetStyle\":{},\"widgetCss\":\"\",\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":500},\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showLegend\":false}"

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

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

24
application/src/main/data/json/tenant/dashboards/gateways.json

@ -360,6 +360,30 @@
"color": "#2196f3", "color": "#2196f3",
"settings": {}, "settings": {},
"_hash": 0.7454705362378311 "_hash": 0.7454705362378311
},
{
"name": "lastConnectTime",
"type": "attribute",
"label": "lastConnectTime",
"color": "#4caf50",
"settings": {},
"_hash": 0.7249585632235194
},
{
"name": "lastDisconnectTime",
"type": "attribute",
"label": "lastDisconnectTime",
"color": "#f44336",
"settings": {},
"_hash": 0.9812430092707332
},
{
"name": "active",
"type": "attribute",
"label": "active",
"color": "#ffc107",
"settings": {},
"_hash": 0.9216097189544408
} }
] ]
} }

0
application/src/main/data/upgrade/3.7.1/schema_update.sql → application/src/main/data/upgrade/3.8.0/schema_update.sql

8
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -138,11 +138,11 @@ public class ThingsboardInstallService {
systemDataLoaderService.updateDefaultNotificationConfigs(false); systemDataLoaderService.updateDefaultNotificationConfigs(false);
systemDataLoaderService.updateSecuritySettings(); systemDataLoaderService.updateSecuritySettings();
case "3.7.0": case "3.7.0":
log.info("Upgrading ThingsBoard from version 3.7.0 to 3.7.1 ..."); log.info("Upgrading ThingsBoard from version 3.7.0 to 3.8.0 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.7.0"); databaseEntitiesUpgradeService.upgradeDatabase("3.7.0");
case "3.7.1": case "3.8.0":
log.info("Upgrading ThingsBoard from version 3.7.1 to 3.7.2 ..."); log.info("Upgrading ThingsBoard from version 3.8.0 to 3.9.0 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.7.1"); databaseEntitiesUpgradeService.upgradeDatabase("3.8.0");
//TODO DON'T FORGET to update switch statement in the CacheCleanupService if you need to clear the cache //TODO DON'T FORGET to update switch statement in the CacheCleanupService if you need to clear the cache
break; break;
default: default:

46
application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java

@ -37,13 +37,10 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.SortOrder;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService; import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID;
@Service @Service
@AllArgsConstructor @AllArgsConstructor
@ -176,20 +173,20 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb
} }
@Override @Override
public List<AlarmId> unassignDeletedUserAlarms(TenantId tenantId, UserId userId, String userTitle, long unassignTs) { public void unassignDeletedUserAlarms(TenantId tenantId, UserId userId, String userTitle, List<UUID> alarms, long unassignTs) {
List<AlarmId> totalAlarmIds = new ArrayList<>(); for (UUID alarmId : alarms) {
PageLink pageLink = new PageLink(100, 0, null, new SortOrder("id", SortOrder.Direction.ASC)); log.trace("[{}] Unassigning alarm {} from user {}", tenantId, alarmId, userId);
while (true) { AlarmApiCallResult result = alarmSubscriptionService.unassignAlarm(tenantId, new AlarmId(alarmId), unassignTs);
PageData<AlarmId> pageData = alarmService.findAlarmIdsByAssigneeId(tenantId, userId, pageLink); if (!result.isSuccessful()) {
List<AlarmId> alarmIds = pageData.getData(); log.error("[{}] Cannot unassign alarm {} from user {}", tenantId, alarmId, userId);
if (alarmIds.isEmpty()) { continue;
break; }
if (result.isModified()) {
String comment = String.format("Alarm was unassigned because user %s - was deleted", userTitle);
addSystemAlarmComment(result.getAlarm(), null, "ASSIGN", comment);
logEntityActionService.logEntityAction(result.getAlarm().getTenantId(), result.getAlarm().getOriginator(), result.getAlarm(), result.getAlarm().getCustomerId(), ActionType.ALARM_UNASSIGNED, null);
} }
processAlarmsUnassignment(tenantId, userId, userTitle, alarmIds, unassignTs);
totalAlarmIds.addAll(alarmIds);
pageLink = pageLink.nextPageLink();
} }
return totalAlarmIds;
} }
@Override @Override
@ -204,22 +201,6 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb
return ts > 0 ? ts : System.currentTimeMillis(); return ts > 0 ? ts : System.currentTimeMillis();
} }
private void processAlarmsUnassignment(TenantId tenantId, UserId userId, String userTitle, List<AlarmId> alarmIds, long unassignTs) {
for (AlarmId alarmId : alarmIds) {
log.trace("[{}] Unassigning alarm {} userId {}", tenantId, alarmId, userId);
AlarmApiCallResult result = alarmSubscriptionService.unassignAlarm(tenantId, alarmId, unassignTs);
if (!result.isSuccessful()) {
log.error("[{}] Cannot unassign alarm {} userId {}", tenantId, alarmId, userId);
continue;
}
if (result.isModified()) {
String comment = String.format("Alarm was unassigned because user %s - was deleted", userTitle);
addSystemAlarmComment(result.getAlarm(), null, "ASSIGN", comment);
logEntityActionService.logEntityAction(result.getAlarm().getTenantId(), result.getAlarm().getOriginator(), result.getAlarm(), result.getAlarm().getCustomerId(), ActionType.ALARM_UNASSIGNED, null);
}
}
}
private void addSystemAlarmComment(Alarm alarm, User user, String subType, String commentText) { private void addSystemAlarmComment(Alarm alarm, User user, String subType, String commentText) {
addSystemAlarmComment(alarm, user, subType, commentText, null); addSystemAlarmComment(alarm, user, subType, commentText, null);
} }
@ -245,4 +226,5 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb
log.error("Failed to save alarm comment", e); log.error("Failed to save alarm comment", e);
} }
} }
} }

4
application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java

@ -19,11 +19,11 @@ 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.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import java.util.List; import java.util.List;
import java.util.UUID;
public interface TbAlarmService { public interface TbAlarmService {
@ -41,7 +41,7 @@ public interface TbAlarmService {
AlarmInfo unassign(Alarm alarm, long unassignTs, User user) throws ThingsboardException; AlarmInfo unassign(Alarm alarm, long unassignTs, User user) throws ThingsboardException;
List<AlarmId> unassignDeletedUserAlarms(TenantId tenantId, UserId userId, String userTitle, long unassignTs); void unassignDeletedUserAlarms(TenantId tenantId, UserId userId, String userTitle, List<UUID> alarms, long unassignTs);
Boolean delete(Alarm alarm, User user); Boolean delete(Alarm alarm, User user);
} }

30
application/src/main/java/org/thingsboard/server/service/housekeeper/processor/AlarmsUnassignTaskProcessor.java

@ -21,22 +21,46 @@ import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.housekeeper.AlarmsUnassignHousekeeperTask; import org.thingsboard.server.common.data.housekeeper.AlarmsUnassignHousekeeperTask;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType; import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType;
import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.util.TbPair;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.service.entitiy.alarm.TbAlarmService; import org.thingsboard.server.service.entitiy.alarm.TbAlarmService;
import java.util.List; import java.util.List;
import java.util.UUID;
@Component @Component
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j @Slf4j
public class AlarmsUnassignTaskProcessor extends HousekeeperTaskProcessor<AlarmsUnassignHousekeeperTask> { public class AlarmsUnassignTaskProcessor extends HousekeeperTaskProcessor<AlarmsUnassignHousekeeperTask> {
private final TbAlarmService alarmService; private final TbAlarmService tbAlarmService;
private final AlarmService alarmService;
@Override @Override
public void process(AlarmsUnassignHousekeeperTask task) throws Exception { public void process(AlarmsUnassignHousekeeperTask task) throws Exception {
List<AlarmId> alarms = alarmService.unassignDeletedUserAlarms(task.getTenantId(), (UserId) task.getEntityId(), task.getUserTitle(), task.getTs()); TenantId tenantId = task.getTenantId();
log.debug("[{}][{}] Unassigned {} alarms", task.getTenantId(), task.getEntityId(), alarms.size()); UserId userId = (UserId) task.getEntityId();
if (task.getAlarms() == null) {
AlarmId lastId = null;
long lastCreatedTime = 0;
while (true) {
List<TbPair<UUID, Long>> alarms = alarmService.findAlarmIdsByAssigneeId(tenantId, userId, lastCreatedTime, lastId, 64);
if (alarms.isEmpty()) {
break;
}
housekeeperClient.submitTask(new AlarmsUnassignHousekeeperTask(tenantId, userId, task.getUserTitle(), alarms.stream().map(TbPair::getFirst).toList()));
TbPair<UUID, Long> last = alarms.get(alarms.size() - 1);
lastId = new AlarmId(last.getFirst());
lastCreatedTime = last.getSecond();
log.debug("[{}][{}] Submitted task for unassigning {} alarms", tenantId, userId, alarms.size());
}
} else {
tbAlarmService.unassignDeletedUserAlarms(tenantId, userId, task.getUserTitle(), task.getAlarms(), task.getTs());
log.debug("[{}][{}] Unassigned {} alarms", tenantId, userId, task.getAlarms().size());
}
} }
@Override @Override

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

@ -122,7 +122,7 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
updateSchema("3.6.4", 3006004, "3.7.0", 3007000, null); updateSchema("3.6.4", 3006004, "3.7.0", 3007000, null);
break; break;
case "3.7.0": case "3.7.0":
updateSchema("3.7.0", 3007000, "3.7.1", 3007001, connection -> { updateSchema("3.7.0", 3007000, "3.8.0", 3008000, connection -> {
try { try {
connection.createStatement().execute("UPDATE rule_node SET " + connection.createStatement().execute("UPDATE rule_node SET " +
"configuration = CASE " + "configuration = CASE " +
@ -138,8 +138,8 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
} }
}); });
break; break;
case "3.7.1": case "3.8.0":
updateSchema("3.7.1", 3007001, "3.7.2", 3007002, null); updateSchema("3.8.0", 3008000, "3.9.0", 3009000, null);
break; break;
default: default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);

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

@ -204,7 +204,7 @@ ui:
# Help parameters # Help parameters
help: help:
# Base URL for UI help assets # Base URL for UI help assets
base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.7}" base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.9}"
# Database telemetry parameters # Database telemetry parameters
database: database:

4
application/src/test/java/org/thingsboard/server/controller/ImageControllerTest.java

@ -355,8 +355,8 @@ public class ImageControllerTest extends AbstractControllerTest {
assertThat(previewDescriptor.getMediaType()).isEqualTo("image/png"); assertThat(previewDescriptor.getMediaType()).isEqualTo("image/png");
assertThat(previewDescriptor.getWidth()).isEqualTo(225); assertThat(previewDescriptor.getWidth()).isEqualTo(225);
assertThat(previewDescriptor.getHeight()).isEqualTo(225); assertThat(previewDescriptor.getHeight()).isEqualTo(225);
assertThat(previewDescriptor.getSize()).isEqualTo(53498); assertThat(previewDescriptor.getSize()).isEqualTo(52365);
assertThat(previewDescriptor.getEtag()).isEqualTo("c909e20ba942f95f4ed1ddfcf4307ce846b4a689195c629cd85f2517f46e84f9"); assertThat(previewDescriptor.getEtag()).isEqualTo("f231854ceb5cfce4371c246f7738d35d61770bac106ad3b9ee9b74d19817d09e");
} }
private void checkSvgImageDescriptor(ImageDescriptor imageDescriptor) { private void checkSvgImageDescriptor(ImageDescriptor imageDescriptor) {

7
application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java

@ -37,7 +37,6 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.edge.EdgeService;
@ -46,7 +45,6 @@ import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService; import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService;
import org.thingsboard.server.service.telemetry.AlarmSubscriptionService; import org.thingsboard.server.service.telemetry.AlarmSubscriptionService;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -164,16 +162,13 @@ public class DefaultTbAlarmServiceTest {
AlarmInfo alarm = new AlarmInfo(); AlarmInfo alarm = new AlarmInfo();
alarm.setId(new AlarmId(UUID.randomUUID())); alarm.setId(new AlarmId(UUID.randomUUID()));
when(alarmService.findAlarmIdsByAssigneeId(any(), any(), any()))
.thenReturn(new PageData<>(List.of(alarm.getId()), 0, 1, false))
.thenReturn(new PageData<>(Collections.EMPTY_LIST, 0, 0, false));
when(alarmSubscriptionService.unassignAlarm(any(), any(), anyLong())) when(alarmSubscriptionService.unassignAlarm(any(), any(), anyLong()))
.thenReturn(AlarmApiCallResult.builder().successful(true).modified(true).alarm(alarm).build()); .thenReturn(AlarmApiCallResult.builder().successful(true).modified(true).alarm(alarm).build());
User user = new User(); User user = new User();
user.setEmail("testEmail@gmail.com"); user.setEmail("testEmail@gmail.com");
user.setId(new UserId(UUID.randomUUID())); user.setId(new UserId(UUID.randomUUID()));
service.unassignDeletedUserAlarms(new TenantId(UUID.randomUUID()), user.getId(), user.getTitle(), System.currentTimeMillis()); service.unassignDeletedUserAlarms(new TenantId(UUID.randomUUID()), user.getId(), user.getTitle(), List.of(alarm.getUuidId()), System.currentTimeMillis());
ObjectNode commentNode = JacksonUtil.newObjectNode(); ObjectNode commentNode = JacksonUtil.newObjectNode();
commentNode.put("subtype", "ASSIGN"); commentNode.put("subtype", "ASSIGN");

37
application/src/test/java/org/thingsboard/server/service/housekeeper/HousekeeperServiceTest.java

@ -55,7 +55,6 @@ import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.msg.TbNodeConnectionType;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.relation.RelationTypeGroup;
@ -63,6 +62,7 @@ import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.util.TbPair;
import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.alarm.AlarmDao; import org.thingsboard.server.dao.alarm.AlarmDao;
import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.alarm.AlarmService;
@ -184,23 +184,32 @@ public class HousekeeperServiceTest extends AbstractControllerTest {
Device device = createDevice("test", "test"); Device device = createDevice("test", "test");
UserId userId = customerUserId; UserId userId = customerUserId;
createRelatedData(userId); createRelatedData(userId);
Alarm alarm = Alarm.builder()
.type("test") List<AlarmId> alarms = new ArrayList<>();
.tenantId(tenantId) int count = 112;
.originator(device.getId()) for (int i = 0; i < count; i++) {
.severity(AlarmSeverity.MAJOR) Alarm alarm = Alarm.builder()
.build(); .type("test" + i)
alarm = doPost("/api/alarm", alarm, Alarm.class); .tenantId(tenantId)
AlarmId alarmId = alarm.getId(); .originator(device.getId())
alarm = doPost("/api/alarm/" + alarmId + "/assign/" + userId, "", Alarm.class); .severity(AlarmSeverity.MAJOR)
assertThat(alarm.getAssigneeId()).isEqualTo(userId); .build();
assertThat(alarmService.findAlarmIdsByAssigneeId(tenantId, userId, new PageLink(100)).getData()).isNotEmpty(); alarm = doPost("/api/alarm", alarm, Alarm.class);
AlarmId alarmId = alarm.getId();
alarm = doPost("/api/alarm/" + alarmId + "/assign/" + userId, "", Alarm.class);
assertThat(alarm.getAssigneeId()).isEqualTo(userId);
alarms.add(alarmId);
}
List<AlarmId> assignedAlarms = alarmService.findAlarmIdsByAssigneeId(tenantId, userId, 0, null, 5000).stream()
.map(TbPair::getFirst).map(AlarmId::new).toList();
assertThat(assignedAlarms).size().isEqualTo(count);
assertThat(assignedAlarms).containsAll(alarms);
doDelete("/api/user/" + userId).andExpect(status().isOk()); doDelete("/api/user/" + userId).andExpect(status().isOk());
await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> { await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> {
verifyNoRelatedData(userId); verifyNoRelatedData(userId);
assertThat(alarmService.findAlarmById(tenantId, alarmId).getAssigneeId()).isNull(); assertThat(alarmService.findAlarmIdsByAssigneeId(tenantId, userId, 0, null, 5000)).size().isZero();
}); });
} }

30
application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java

@ -411,27 +411,36 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte
} }
protected void awaitObserveReadAll(int cntObserve, String deviceIdStr) throws Exception { protected void awaitObserveReadAll(int cntObserve, String deviceIdStr) throws Exception {
await("ObserveReadAll after start client/test: countObserve " + cntObserve) await("ObserveReadAll: countObserve " + cntObserve)
.atMost(40, TimeUnit.SECONDS) .atMost(40, TimeUnit.SECONDS)
.until(() -> cntObserve == getCntObserveAll(deviceIdStr)); .until(() -> cntObserve == getCntObserveAll(deviceIdStr));
} }
protected Integer getCntObserveAll(String deviceIdStr) throws Exception { protected Integer getCntObserveAll(String deviceIdStr) throws Exception {
String actualResultBefore = sendObserve("ObserveReadAll", null, deviceIdStr); String actualResult = sendObserveOK("ObserveReadAll", null, deviceIdStr);
ObjectNode rpcActualResultBefore = JacksonUtil.fromString(actualResultBefore, ObjectNode.class); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultBefore.get("result").asText()); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
JsonElement element = JsonUtils.parse(rpcActualResultBefore.get("value").asText()); JsonElement element = JsonUtils.parse(rpcActualResult.get("value").asText());
return element.isJsonArray() ? ((JsonArray)element).size() : null; return element.isJsonArray() ? ((JsonArray)element).size() : null;
} }
protected void sendCancelObserveAllWithAwait(String deviceIdStr) throws Exception { protected void sendObserveCancelAllWithAwait(String deviceIdStr) throws Exception {
String actualResultCancelAll = sendObserve("ObserveCancelAll", null, deviceIdStr); String actualResultCancelAll = sendObserveOK("ObserveCancelAll", null, deviceIdStr);
ObjectNode rpcActualResultCancelAll = JacksonUtil.fromString(actualResultCancelAll, ObjectNode.class); ObjectNode rpcActualResultCancelAll = JacksonUtil.fromString(actualResultCancelAll, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultCancelAll.get("result").asText()); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultCancelAll.get("result").asText());
awaitObserveReadAll(0, deviceId); awaitObserveReadAll(0, deviceId);
} }
protected String sendObserve(String method, String params, String deviceIdStr) throws Exception { protected String sendRpcObserveOkWithResultValue(String method, String params) throws Exception {
String actualResultReadAll = sendRpcObserveOk(method, params);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
return rpcActualResult.get("value").asText();
}
protected String sendRpcObserveOk(String method, String params) throws Exception {
return sendObserveOK(method, params, deviceId);
}
protected String sendObserveOK(String method, String params, String deviceIdStr) throws Exception {
String sendRpcRequest; String sendRpcRequest;
if (params == null) { if (params == null) {
sendRpcRequest = "{\"method\": \"" + method + "\"}"; sendRpcRequest = "{\"method\": \"" + method + "\"}";
@ -442,4 +451,9 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte
return doPostAsync("/api/plugins/rpc/twoway/" + deviceIdStr, sendRpcRequest, String.class, status().isOk()); return doPostAsync("/api/plugins/rpc/twoway/" + deviceIdStr, sendRpcRequest, String.class, status().isOk());
} }
protected ObjectNode sendRpcObserveWithResult(String method, String params) throws Exception {
String actualResultReadAll = sendRpcObserveOk(method, params);
return JacksonUtil.fromString(actualResultReadAll, ObjectNode.class);
}
} }

1
application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java

@ -48,6 +48,7 @@ public class Lwm2mTestHelper {
public static final String RESOURCE_ID_NAME_3_9 = "batteryLevel"; public static final String RESOURCE_ID_NAME_3_9 = "batteryLevel";
public static final String RESOURCE_ID_NAME_3_14 = "UtfOffset"; public static final String RESOURCE_ID_NAME_3_14 = "UtfOffset";
public static final String RESOURCE_ID_NAME_19_0_0 = "dataRead"; public static final String RESOURCE_ID_NAME_19_0_0 = "dataRead";
public static final String RESOURCE_ID_NAME_19_0_2 = "dataCreationTime";
public static final String RESOURCE_ID_NAME_19_1_0 = "dataWrite"; public static final String RESOURCE_ID_NAME_19_1_0 = "dataWrite";
public static final String RESOURCE_ID_NAME_19_0_3 = "dataDescription"; public static final String RESOURCE_ID_NAME_19_0_3 = "dataDescription";

19
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java

@ -27,8 +27,10 @@ import org.eclipse.leshan.core.response.WriteResponse;
import javax.security.auth.Destroyable; import javax.security.auth.Destroyable;
import java.sql.Time; import java.sql.Time;
import java.time.Instant;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -85,7 +87,8 @@ public class LwM2mBinaryAppDataContainer extends BaseInstanceEnabler implements
fireResourceChange(0); fireResourceChange(0);
fireResourceChange(2); fireResourceChange(2);
} }
, 1800000, 1800000, TimeUnit.MILLISECONDS); // 30 MIN , 1, 1, TimeUnit.SECONDS); // 1 sec
// , 1800000, 1800000, TimeUnit.MILLISECONDS); // 30 MIN
} catch (Throwable e) { } catch (Throwable e) {
log.error("[{}]Throwable", e.toString()); log.error("[{}]Throwable", e.toString());
e.printStackTrace(); e.printStackTrace();
@ -123,6 +126,7 @@ public class LwM2mBinaryAppDataContainer extends BaseInstanceEnabler implements
switch (resourceId) { switch (resourceId) {
case 0: case 0:
if (setData(value, replace)) { if (setData(value, replace)) {
fireResourceChange(resourceId);
return WriteResponse.success(); return WriteResponse.success();
} else { } else {
WriteResponse.badRequest("Invalidate value ..."); WriteResponse.badRequest("Invalidate value ...");
@ -132,7 +136,7 @@ public class LwM2mBinaryAppDataContainer extends BaseInstanceEnabler implements
fireResourceChange(resourceId); fireResourceChange(resourceId);
return WriteResponse.success(); return WriteResponse.success();
case 2: case 2:
setTimestamp(((Date) value.getValue()).getTime()); setTimestamp();
fireResourceChange(resourceId); fireResourceChange(resourceId);
return WriteResponse.success(); return WriteResponse.success();
case 3: case 3:
@ -177,12 +181,15 @@ public class LwM2mBinaryAppDataContainer extends BaseInstanceEnabler implements
return this.description; return this.description;
} }
private void setTimestamp(long time) { private void setTimestamp() {
this.timestamp = new Time(time); long currentTimeMillis = System.currentTimeMillis();
this.timestamp = new Time(currentTimeMillis);
} }
private Time getTimestamp() { private Time getTimestamp() {
return this.timestamp != null ? this.timestamp : new Time(new Date().getTime()); LocalTime localTime = LocalTime.ofInstant(Instant.now(), ZoneId.systemDefault());
this.timestamp = Time.valueOf(localTime);
return this.timestamp;
} }
private boolean setData(LwM2mResource value, boolean replace) { private boolean setData(LwM2mResource value, boolean replace) {

7
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java

@ -58,7 +58,7 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl
executorService.scheduleWithFixedDelay(() -> { executorService.scheduleWithFixedDelay(() -> {
fireResourceChange(9); fireResourceChange(9);
} }
, 1, 1, TimeUnit.SECONDS); // 30 MIN , 1, 1, TimeUnit.SECONDS); // 2 sec
// , 1800000, 1800000, TimeUnit.MILLISECONDS); // 30 MIN // , 1800000, 1800000, TimeUnit.MILLISECONDS); // 30 MIN
} catch (Throwable e) { } catch (Throwable e) {
log.error("[{}]Throwable", e.toString()); log.error("[{}]Throwable", e.toString());
@ -169,8 +169,9 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl
} }
private int getBatteryLevel() { private int getBatteryLevel() {
return randomIterator.nextInt(); int valBattery = randomIterator.nextInt();
// return 42; log.trace("Send from client [3/0/9] val: [{}]", valBattery);
return valBattery;
} }
private long getMemoryFree() { private long getMemoryFree() {

2
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationObserveTest.java

@ -28,7 +28,7 @@ public abstract class AbstractRpcLwM2MIntegrationObserveTest extends AbstractRpc
@Before @Before
public void initTest () throws Exception { public void initTest () throws Exception {
awaitObserveReadAll(2, deviceId); awaitObserveReadAll(4, deviceId);
} }
} }

19
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java

@ -42,13 +42,16 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INST
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_1; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_1;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_14; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_14;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_2;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_9; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_9;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_0_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_0_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_0_2;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_1_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_1_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_14; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_14;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_9; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_9;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.TEMPERATURE_SENSOR; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.TEMPERATURE_SENSOR;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resources; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resources;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId;
@DaoSqlTest @DaoSqlTest
public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
@ -74,7 +77,11 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg
protected String objectIdVer_3303; protected String objectIdVer_3303;
protected static AtomicInteger endpointSequence = new AtomicInteger(); protected static AtomicInteger endpointSequence = new AtomicInteger();
protected static String DEVICE_ENDPOINT_RPC_PREF = "deviceEndpointRpc"; protected static String DEVICE_ENDPOINT_RPC_PREF = "deviceEndpointRpc";
protected String idVer_3_0_0;
protected String idVer_3_0_9; protected String idVer_3_0_9;
protected String id_3_0_9;
protected String idVer_19_0_0; protected String idVer_19_0_0;
public AbstractRpcLwM2MIntegrationTest() { public AbstractRpcLwM2MIntegrationTest() {
@ -125,7 +132,9 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg
objectInstanceIdVer_5 = (String) expectedObjectIdVerInstances.stream().filter(path -> ((String) path).startsWith("/" + FIRMWARE)).findFirst().get(); objectInstanceIdVer_5 = (String) expectedObjectIdVerInstances.stream().filter(path -> ((String) path).startsWith("/" + FIRMWARE)).findFirst().get();
objectInstanceIdVer_9 = (String) expectedObjectIdVerInstances.stream().filter(path -> ((String) path).startsWith("/" + SOFTWARE_MANAGEMENT)).findFirst().get(); objectInstanceIdVer_9 = (String) expectedObjectIdVerInstances.stream().filter(path -> ((String) path).startsWith("/" + SOFTWARE_MANAGEMENT)).findFirst().get();
idVer_3_0_0 = objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0;
idVer_3_0_9 = objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_9; idVer_3_0_9 = objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_9;
id_3_0_9 = fromVersionedIdToObjectId(idVer_3_0_9);
idVer_19_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0; idVer_19_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0;
OBSERVE_ATTRIBUTES_WITH_PARAMS_RPC = OBSERVE_ATTRIBUTES_WITH_PARAMS_RPC =
@ -134,14 +143,18 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg
" \"" + idVer_3_0_9 + "\": \"" + RESOURCE_ID_NAME_3_9 + "\",\n" + " \"" + idVer_3_0_9 + "\": \"" + RESOURCE_ID_NAME_3_9 + "\",\n" +
" \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_14 + "\": \"" + RESOURCE_ID_NAME_3_14 + "\",\n" + " \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_14 + "\": \"" + RESOURCE_ID_NAME_3_14 + "\",\n" +
" \"" + idVer_19_0_0 + "\": \"" + RESOURCE_ID_NAME_19_0_0 + "\",\n" + " \"" + idVer_19_0_0 + "\": \"" + RESOURCE_ID_NAME_19_0_0 + "\",\n" +
" \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\": \"" + RESOURCE_ID_NAME_19_1_0 + "\"\n" + " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\": \"" + RESOURCE_ID_NAME_19_1_0 + "\",\n" +
" \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_2 + "\": \"" + RESOURCE_ID_NAME_19_0_2 + "\"\n" +
" },\n" + " },\n" +
" \"observe\": [\n" + " \"observe\": [\n" +
" \"" + idVer_3_0_9 + "\",\n" + " \"" + idVer_3_0_9 + "\",\n" +
" \"" + idVer_19_0_0 + "\"\n" + " \"" + idVer_19_0_0 + "\",\n" +
" \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\",\n" +
" \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_2 + "\"\n" +
" ],\n" + " ],\n" +
" \"attribute\": [\n" + " \"attribute\": [\n" +
" \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_14 + "\"\n" + " \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_14 + "\",\n" +
" \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_2 + "\"\n" +
" ],\n" + " ],\n" +
" \"telemetry\": [\n" + " \"telemetry\": [\n" +
" \"" + idVer_3_0_9 + "\",\n" + " \"" + idVer_3_0_9 + "\",\n" +

502
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2MIntegrationObserveCompositeTest.java

@ -15,12 +15,22 @@
*/ */
package org.thingsboard.server.transport.lwm2m.rpc.sql; package org.thingsboard.server.transport.lwm2m.rpc.sql;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.ResponseCode; import org.eclipse.leshan.core.ResponseCode;
import org.junit.Test; import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationObserveTest; import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationObserveTest;
import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2mUplinkMsgHandler;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import static org.awaitility.Awaitility.await;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@ -29,21 +39,25 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INST
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_1; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_1;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_14; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_14;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_15;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_2; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_2;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_5; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_5;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_7; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_7;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_9; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_9;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_0_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_0_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_0_2;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_1_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_1_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_14; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_14;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_9; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_9;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_INSTANCE_ID_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_INSTANCE_ID_0;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId; import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId;
@Slf4j
public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MIntegrationObserveTest { public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MIntegrationObserveTest {
@SpyBean
DefaultLwM2mUplinkMsgHandler defaultUplinkMsgHandlerTest;
/** /**
* ObserveComposite {"ids":["5/0/7", "5/0/5", "5/0/3", "3/0/9", "19/1/0/0"]} - Ok * ObserveComposite {"ids":["5/0/7", "5/0/5", "5/0/3", "3/0/9", "19/1/0/0"]} - Ok
@ -51,13 +65,13 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt
*/ */
@Test @Test
public void testObserveCompositeAnyResources_Result_CONTENT_Value_LwM2mSingleResource_LwM2mResourceInstance() throws Exception { public void testObserveCompositeAnyResources_Result_CONTENT_Value_LwM2mSingleResource_LwM2mResourceInstance() throws Exception {
sendCancelObserveAllWithAwait(deviceId); sendObserveCancelAllWithAwait(deviceId);
String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7; String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7;
String expectedIdVer5_0_5= objectInstanceIdVer_5 + "/" + RESOURCE_ID_5; String expectedIdVer5_0_5 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_5;
String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3;
String expectedIdVer19_1_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "/" + RESOURCE_INSTANCE_ID_0; String expectedIdVer19_1_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "/" + RESOURCE_INSTANCE_ID_0;
String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_5 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + idVer_3_0_9 + "\", \"" + expectedIdVer19_1_0_0 + "\"]"; String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_5 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + idVer_3_0_9 + "\", \"" + expectedIdVer19_1_0_0 + "\"]";
String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String actualValues = rpcActualResult.get("value").asText(); String actualValues = rpcActualResult.get("value").asText();
@ -74,30 +88,30 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt
*/ */
@Test @Test
public void testObserveComposite_ObjectInstanceWithOtherObjectResourceInstance_Result_CONTENT_Ok() throws Exception { public void testObserveComposite_ObjectInstanceWithOtherObjectResourceInstance_Result_CONTENT_Ok() throws Exception {
sendCancelObserveAllWithAwait(deviceId); sendObserveCancelAllWithAwait(deviceId);
String expectedIdVer19_1_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0; String expectedIdVer19_1_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0;
String expectedIdVer5_0 = objectInstanceIdVer_5; String expectedIdVer5_0 = objectInstanceIdVer_5;
String expectedIds = "[\"" + expectedIdVer19_1_0 + "\", \"" + expectedIdVer5_0 + "\"]"; String expectedIds = "[\"" + expectedIdVer19_1_0 + "\", \"" + expectedIdVer5_0 + "\"]";
String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String actual= rpcActualResult.get("value").asText(); String actual = rpcActualResult.get("value").asText();
assertTrue(actual.contains(fromVersionedIdToObjectId(expectedIdVer19_1_0) + "=LwM2mMultipleResource")); assertTrue(actual.contains(fromVersionedIdToObjectId(expectedIdVer19_1_0) + "=LwM2mMultipleResource"));
assertTrue(actual.contains(fromVersionedIdToObjectId(expectedIdVer5_0) + "=LwM2mObjectInstance")); assertTrue(actual.contains(fromVersionedIdToObjectId(expectedIdVer5_0) + "=LwM2mObjectInstance"));
} }
/** /**
* ObserveComposite {"ids":["5/0/7", "5/0/2"]} - Ok * ObserveComposite {"ids":["5/0/7", "5/0/2"]} - Ok
* "5/0/2" - Execute^ result == null * "5/0/2" - Execute result == null
* @throws Exception * @throws Exception
*/ */
@Test @Test
public void testObserveCompositeAnyResources_Result_CONTENT_Value_LwM2mSingleResource_If_Error_Null() throws Exception { public void testObserveReadAll_AfterCompositeObservation_WithResourceNotReadable_Result_CONTENT_ObserveResourceNotReadableIsNull() throws Exception {
// sendCancelObserveAllWithAwait(deviceId); sendObserveCancelAllWithAwait(deviceId);
String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7; String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7;
String expectedIdVer5_0_2 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_2; String expectedIdVer5_0_2 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_2;
String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_2 + "\"]"; String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_2 + "\"]";
String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String actualValues = rpcActualResult.get("value").asText(); String actualValues = rpcActualResult.get("value").asText();
@ -113,34 +127,102 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt
*/ */
@Test @Test
public void testObserveComposite_Result_BAD_REQUEST_ONE_PATH_CONTAINCE_OTHER() throws Exception { public void testObserveComposite_Result_BAD_REQUEST_ONE_PATH_CONTAINCE_OTHER() throws Exception {
sendCancelObserveAllWithAwait(deviceId); sendObserveCancelAllWithAwait(deviceId);
String expectedIdVer5_0 = objectInstanceIdVer_5; String expectedIdVer5_0 = objectInstanceIdVer_5;
String expectedIdVer5_0_2 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_2; String expectedIdVer5_0_2 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_2;
String expectedIds = "[\"" + expectedIdVer5_0 + "\", \"" + expectedIdVer5_0_2 + "\"]"; String expectedIds = "[\"" + expectedIdVer5_0 + "\", \"" + expectedIdVer5_0_2 + "\"]";
String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String actual= rpcActualResult.get("error").asText(); String actual = rpcActualResult.get("error").asText();
String expected = "Invalid path list : /5/0 and /5/0/2 are overlapped paths"; String expected = "Invalid path list : /5/0 and /5/0/2 are overlapped paths";
assertTrue(expected.equals(actual)); assertTrue(expected.equals(actual));
} }
/** /**
* Previous -> "3/0/9" * Previous -> "3/0/9", "19/0/2", "19/1/0", "19/0/0", All only SingleObservation;
* ObserveComposite {"ids":["5/0/7", "5/0/5", "5/0/3", "3/0/9"]} - CONTENT * if at least one of the resource objectIds (Composite) in SingleObservation or CompositeObservation is already registered - return BAD REQUEST
* ObserveComposite {"ids":["5/0/7", "5/0/5", "5/0/3", "3/0/9"]}
* @throws Exception * @throws Exception
*/ */
@Test @Test
public void testObserveCompositeThereAreObservationOneResource_Result_CONTENT_Value_ObservationAddIfAbsent() throws Exception { public void testObserveComposite_IfLeastOneResourceIsAlreadyRegistered_return_BadRequest() throws Exception {
// Verify after start
String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null);
ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText());
String actualValues = rpcActualResultReadAll.get("value").asText();
String expectedIdVer19_0_2 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_2;
String expectedIdVer19_1_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0;
assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(idVer_3_0_9)));
assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer19_0_2)));
assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer19_1_0)));
assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(idVer_19_0_0)));
// Send Observe composite with "/3/0/9"
String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7; String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7;
String expectedIdVer5_0_5= objectInstanceIdVer_5 + "/" + RESOURCE_ID_5; String expectedIdVer5_0_5 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_5;
String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3;
String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_5 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + idVer_3_0_9 + "\"]"; String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_5 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + idVer_3_0_9 + "\"]";
String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
assertTrue(rpcActualResult.get("error").asText().contains(fromVersionedIdToObjectId(idVer_3_0_9)));
assertTrue(rpcActualResult.get("error").asText().contains("is already registered"));
// verify after send Observe composite
actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null);
rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText());
actualValues = rpcActualResultReadAll.get("value").asText();
assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(idVer_3_0_9)));
assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer19_0_2)));
assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer19_1_0)));
assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(idVer_19_0_0)));
}
/**
* Previous -> ["5/0/7", "5/0/5", "5/0/3"], CompositeObservation *
* if the resource SingleObservation is already registered in CompositeObservation - return BAD REQUEST
* SingleObservation {"id":["5/0/7"}
* @throws Exception
*/
@Test
public void testObserveSingle_IfResourceIsAlreadyRegisteredInComposite_return_BadRequest() throws Exception {
// Send Observe Composite
String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7;
String expectedId5_0_7 = fromVersionedIdToObjectId(expectedIdVer5_0_7);
String expectedIdVer5_0_5 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_5;
String expectedId5_0_5 = fromVersionedIdToObjectId(expectedIdVer5_0_5);
String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3;
String expectedId5_0_3 = fromVersionedIdToObjectId(expectedIdVer5_0_3);
String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_5 + "\", \"" + expectedIdVer5_0_3 + "\"]";
String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String expectedResult = "/3/0/9=LwM2mSingleResource [id=9"; String actualValues = rpcActualResult.get("value").asText();
assertTrue(rpcActualResult.get("value").asText().contains(expectedResult)); assertTrue(actualValues.contains(expectedId5_0_7));
assertTrue(actualValues.contains(expectedId5_0_5));
assertTrue(actualValues.contains(expectedId5_0_3));
// Send Observe Single
actualResult = sendObserve("Observe", expectedIdVer5_0_7);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
assertTrue(rpcActualResult.get("error").asText().contains(expectedId5_0_7));
assertTrue(rpcActualResult.get("error").asText().contains("is already registered"));
// verify after send Observe Single
String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null);
ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText());
actualValues = rpcActualResultReadAll.get("value").asText();
String expectedIdVer19_0_2 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_2;
String expectedIdVer19_1_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0;
assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(idVer_3_0_9)));
assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer19_0_2)));
assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer19_1_0)));
assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(idVer_19_0_0)));
assertTrue(actualValues.contains("CompositeObservation:"));
assertTrue(actualValues.contains(expectedId5_0_7));
assertTrue(actualValues.contains(expectedId5_0_5));
assertTrue(actualValues.contains(expectedId5_0_3));
} }
/** /**
@ -149,14 +231,14 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt
*/ */
@Test @Test
public void testObserveCompositeAnyResources_Result_CONTENT_Value_LwM2mSingleResource_LwM2mMultipleResource() throws Exception { public void testObserveCompositeAnyResources_Result_CONTENT_Value_LwM2mSingleResource_LwM2mMultipleResource() throws Exception {
sendCancelObserveAllWithAwait(deviceId); sendObserveCancelAllWithAwait(deviceId);
String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7; String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7;
String expectedIdVer5_0_5= objectInstanceIdVer_5 + "/" + RESOURCE_ID_5; String expectedIdVer5_0_5 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_5;
String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3;
String expectedIdVer19_1_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0; String expectedIdVer19_1_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0;
String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_5 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + idVer_3_0_9 + "\", \"" + expectedIdVer19_1_0 + "\"]"; String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_5 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + idVer_3_0_9 + "\", \"" + expectedIdVer19_1_0 + "\"]";
String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String actualValues = rpcActualResult.get("value").asText(); String actualValues = rpcActualResult.get("value").asText();
@ -173,7 +255,7 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt
*/ */
@Test @Test
public void testObserveCompositeWithKeyName_Result_CONTENT_Value_SingleResources() throws Exception { public void testObserveCompositeWithKeyName_Result_CONTENT_Value_SingleResources() throws Exception {
sendCancelObserveAllWithAwait(deviceId); sendObserveCancelAllWithAwait(deviceId);
String expectedKey3_0_9 = RESOURCE_ID_NAME_3_9; String expectedKey3_0_9 = RESOURCE_ID_NAME_3_9;
String expectedKey3_0_14 = RESOURCE_ID_NAME_3_14; String expectedKey3_0_14 = RESOURCE_ID_NAME_3_14;
@ -198,18 +280,16 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt
* @throws Exception * @throws Exception
*/ */
@Test @Test
public void testObserveCompositeWithKeyNameThereAreObservationOneResource_Result_CONTENT_Value_ObservationAddIfAbsent() throws Exception { public void testObserveCompositeWithKeyName_IfLeastOneResourceIsAlreadyRegistered_return_BadRequest() throws Exception {
String expectedKey3_0_9 = RESOURCE_ID_NAME_3_9; String expectedKey3_0_9 = RESOURCE_ID_NAME_3_9;
String expectedKey3_0_14 = RESOURCE_ID_NAME_3_14; String expectedKey3_0_14 = RESOURCE_ID_NAME_3_14;
String expectedKey19_0_0 = RESOURCE_ID_NAME_19_0_0; String expectedKey19_0_0 = RESOURCE_ID_NAME_19_0_0;
String expectedKey19_1_0 = RESOURCE_ID_NAME_19_1_0; String expectedKey19_1_0 = RESOURCE_ID_NAME_19_1_0;
String expectedKeys = "[\"" + expectedKey3_0_9 + "\", \"" + expectedKey3_0_14 + "\", \"" + expectedKey19_0_0 + "\", \"" + expectedKey19_1_0 + "\"]"; String expectedKeys = "[\"" + expectedKey3_0_9 + "\", \"" + expectedKey3_0_14 + "\", \"" + expectedKey19_0_0 + "\", \"" + expectedKey19_1_0 + "\"]";
String expectedIdVer19_1_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0;
String actualResult = sendCompositeRPCByKeys("ObserveComposite", expectedKeys); String actualResult = sendCompositeRPCByKeys("ObserveComposite", expectedKeys);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String actual = rpcActualResult.get("value").asText(); assertTrue(rpcActualResult.get("error").asText().contains("is already registered"));
assertTrue(actual.contains(fromVersionedIdToObjectId(expectedIdVer19_1_0) + "=LwM2mMultipleResource"));
} }
/** /**
@ -218,8 +298,8 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt
* @throws Exception * @throws Exception
*/ */
@Test @Test
public void testObserveReadAll_AfterCompositeObservation_Result_CONTENT_Value_SingleObservation_Only() throws Exception { public void testObserveReadAll_AfterbserveCancelAllAndCompositeObservation_Result_CONTENT_Value_CompositeObservation_Only() throws Exception {
sendCancelObserveAllWithAwait(deviceId); sendObserveCancelAllWithAwait(deviceId);
String expectedKey3_0_9 = RESOURCE_ID_NAME_3_9; String expectedKey3_0_9 = RESOURCE_ID_NAME_3_9;
String expectedKey3_0_14 = RESOURCE_ID_NAME_3_14; String expectedKey3_0_14 = RESOURCE_ID_NAME_3_14;
@ -232,76 +312,15 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt
String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null); String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null);
ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText());
String actualValues = rpcActualResultReadAll.get("value").asText(); String actualValues = rpcActualResultReadAll.get("value").asText();
String expectedIdVer3_0_14 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_14; String expectedIdVer3_0_14 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_14;
String expectedIdVer19_1_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0; String expectedIdVer19_1_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0;
assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(idVer_3_0_9))); assertTrue(actualValues.contains("CompositeObservation:"));
assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer3_0_14))); assertFalse(actualValues.contains("SingleObservation"));
assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer19_1_0))); assertTrue(actualValues.contains(Objects.requireNonNull(fromVersionedIdToObjectId(idVer_3_0_9))));
assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(idVer_19_0_0))); assertTrue(actualValues.contains(Objects.requireNonNull(fromVersionedIdToObjectId(expectedIdVer3_0_14))));
} assertTrue(actualValues.contains(Objects.requireNonNull(fromVersionedIdToObjectId(expectedIdVer19_1_0))));
assertTrue(actualValues.contains(Objects.requireNonNull(fromVersionedIdToObjectId(idVer_19_0_0))));
/**
* ObserveReadAll
* {"result":"CONTENT","value":"{"result":"CONTENT","value":"["SingleObservation:/3/0/9","SingleObservation:/3/0/14","SingleObservation:/19/1/0/0","SingleObservation:/19/0/0"]"} - Ok
* @throws Exception
*/
@Test
public void testObserveReadAll_Result_CONTENT_Value_SingleObservation_Only() throws Exception {
sendCancelObserveAllWithAwait(deviceId);
String expectedIdVer3_0_14 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_14;
String expectedIdVer19_1_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "/" + RESOURCE_INSTANCE_ID_0;
String actualResult3_0_9 = sendObserve("Observe", idVer_3_0_9);
ObjectNode rpcActualResult3_0_9 = JacksonUtil.fromString(actualResult3_0_9, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult3_0_9.get("result").asText());
String actualResult3_0_14 = sendObserve("Observe", expectedIdVer3_0_14);
ObjectNode rpcActualResult3_0_14 = JacksonUtil.fromString(actualResult3_0_14, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult3_0_14.get("result").asText());
String actualResult19_1_0_0 = sendObserve("Observe", expectedIdVer19_1_0_0);
ObjectNode rpcActualResult19_1_0_0 = JacksonUtil.fromString(actualResult19_1_0_0, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult19_1_0_0.get("result").asText());
String actualResult19_0_0 = sendObserve("Observe", idVer_19_0_0);
ObjectNode rpcActualResult19_0_0 = JacksonUtil.fromString(actualResult19_0_0, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult19_0_0.get("result").asText());
String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null);
ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText());
String actualValues = rpcActualResultReadAll.get("value").asText();
assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(idVer_3_0_9)));
assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer3_0_14)));
assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer19_1_0_0)));
assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(idVer_19_0_0)));
}
/**
* ObserveReadAll
* {"result":"CONTENT","value":"[\"CompositeObservation: [/19/1/0\",\"/19/0/0\",\"/3/0/14\",\"/3/0/9]\"]"} - Ok
* @throws Exception
*/
@Test
public void testObserveReadAll_AfterCompositeObservation_WithResourceNotReadable_Result_CONTENT_Value_SingleObservation_Only() throws Exception {
sendCancelObserveAllWithAwait(deviceId);
String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7;
String expectedIdVer5_0_2= objectInstanceIdVer_5 + "/" + RESOURCE_ID_2;
String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3;
String expectedIdVer19_1_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0;
String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_2 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + idVer_3_0_9 + "\", \"" + expectedIdVer19_1_0 + "\"]";
String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String actualValues = rpcActualResult.get("value").asText();
assertTrue(actualValues.contains(fromVersionedIdToObjectId(expectedIdVer5_0_2) + "=null"));
String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null);
ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText());
actualValues = rpcActualResultReadAll.get("value").asText();
assertFalse(actualValues.contains(fromVersionedIdToObjectId(expectedIdVer5_0_2)));
} }
/** /**
@ -310,166 +329,179 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt
* @throws Exception * @throws Exception
*/ */
@Test @Test
public void testObserveCompositeAnyResources_Result_CONTENT_CancelObserveComposite_This_Result_Content_Count_5() throws Exception { public void testObserveCancelAllThenObserveCompositeAnyResources_Result_CONTENT_CancelObserveComposite_This_Result_Content_Count_1() throws Exception {
sendCancelObserveAllWithAwait(deviceId); sendObserveCancelAllWithAwait(deviceId);
// ObserveComposite // ObserveComposite
String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7; String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7;
String expectedIdVer5_0_5= objectInstanceIdVer_5 + "/" + RESOURCE_ID_5; String expectedIdVer5_0_5 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_5;
String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3;
String expectedIdVer19_1_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "/" + RESOURCE_INSTANCE_ID_0; String expectedIdVer19_1_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "/" + RESOURCE_INSTANCE_ID_0;
String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_5 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + idVer_3_0_9 + "\", \"" + expectedIdVer19_1_0_0 + "\"]"; String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_5 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + idVer_3_0_9 + "\", \"" + expectedIdVer19_1_0_0 + "\"]";
String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
// ObserveCompositeCancel
actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertEquals("5", rpcActualResult.get("value").asText());
assertEquals(0, (Object) getCntObserveAll(deviceId));
}
/**
* ObserveComposite {"ids":["/3", "/5/0/3", "/19/1/0/0"]} - Ok
* ObserveCompositeCancel {"ids":["/3", "/5/0/3", "/19/1/0/0"]} - Ok
* @throws Exception
*/
@Test
public void testObserveCompositeOneObjectAnyResources_Result_CONTENT_CancelObserveComposite_This_Result_Content_Count_3() throws Exception {
sendCancelObserveAllWithAwait(deviceId);
// ObserveComposite
String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3;
String expectedIdVer19_1_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "/" + RESOURCE_INSTANCE_ID_0;
String expectedIds = "[\"" + idVer_3_0_9 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + expectedIdVer19_1_0_0 + "\"]";
String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
// ObserveCompositeCancel // ObserveCompositeCancel
actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds); actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertEquals("3", rpcActualResult.get("value").asText()); assertEquals("1", rpcActualResult.get("value").asText());
assertEquals(0, (Object) getCntObserveAll(deviceId)); assertEquals(0, (Object) getCntObserveAll(deviceId));
} }
/** /**
* ObserveComposite {"ids":["/3/0/9", "/5/0/5", "/5/0/3", "/5/0/7", "/19/1/0/0"]} - Ok * ObserveComposite {"ids":["/3/0/9", "/5/0/5", "/5/0/3", "/5/0/7", "/19/1/0/0"]} - Ok
* ObserveCompositeCancel {"ids":["/5", "/19/1/0/0"]} - Ok * ObserveCompositeCancel {"ids":["/5", "/19/1/0/0"]} - BadRequest
* last Observation
* @throws Exception * @throws Exception
*/ */
@Test @Test
public void testObserveCompositeAnyResources_Result_CONTENT_CancelObserveComposite_OneObjectAnyResource_Result_Content_Count_4() throws Exception { public void testObserveCompositeFiveResources_Result_CONTENT_CancelObserveComposite_TwoAnyResource_Result_BadRequest() throws Exception {
sendCancelObserveAllWithAwait(deviceId); sendObserveCancelAllWithAwait(deviceId);
// ObserveComposite // ObserveComposite five
String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7; String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7;
String expectedIdVer5_0_5 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_5; String expectedIdVer5_0_5 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_5;
String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3;
String expectedIdVer3_0_9 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9; String expectedIdVer3_0_9 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9;
String expectedIdVer19_1_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "/" + RESOURCE_INSTANCE_ID_0; String expectedIdVer19_1_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "/" + RESOURCE_INSTANCE_ID_0;
String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_5 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + expectedIdVer3_0_9 + "\", \"" + expectedIdVer19_1_0_0 + "\"]"; String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_5 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + expectedIdVer3_0_9 + "\", \"" + expectedIdVer19_1_0_0 + "\"]";
String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
awaitObserveReadAll(5, deviceId); awaitObserveReadAll(1, deviceId);
// ObserveCompositeCancel // ObserveCompositeCancel two
expectedIds = "[\"" + objectInstanceIdVer_5 + "\", \"" + expectedIdVer19_1_0_0 + "\"]"; expectedIds = "[\"" + objectInstanceIdVer_5 + "\", \"" + expectedIdVer19_1_0_0 + "\"]";
actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds); actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
assertEquals("4", rpcActualResult.get("value").asText()); // CNT = 4 ("/5/0/5", "/5/0/3", "/5/0/7", "/19/1/0/0"9) assertTrue(rpcActualResult.get("error").asText().contains(objectInstanceIdVer_5)); // CNT = 4 ("/5/0/5", "/5/0/3", "/5/0/7", "/19/1/0/0"9)
assertTrue(rpcActualResult.get("error").asText().contains(expectedIdVer19_1_0_0)); // CNT = 4 ("/5/0/5", "/5/0/3", "/5/0/7", "/19/1/0/0"9)
String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null);
ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText());
String actualValues = rpcActualResultReadAll.get("value").asText();
assertEquals("[\"SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer3_0_9) + "\"]", actualValues);
} }
/** /**
* ObserveComposite {"ids":["/3", "/5/0/3", "/19/1/0/0"]} - Ok * ObserveComposite {"ids":["/3", "/5/0/3", "/19/1/0/0"]} - Ok
* ObserveCompositeCancel {"ids":["/3/0/9", "/5/0/3", "/19/1/0/0"} -> BAD_REQUEST * ObserveCompositeCancel {"ids":["/19/1/0/0", "/3/0/9"} -> BAD_REQUEST
* ObserveCompositeCancel {"ids":["/3"} -> CONTENT
*/ */
@Test @Test
public void testObserveOneObjectAnyResources_Result_CONTENT_Cancel_OneResourceFromObjectAnyResource_Result_BAD_REQUEST_Cancel_OneObject_Result_CONTENT() throws Exception { public void testObserveOneObjectAnyResources_Result_CONTENT_Cancel_OneResourceFromObjectAnyResource_Result_BAD_REQUEST_Cancel_OneObject_Result_CONTENT() throws Exception {
sendCancelObserveAllWithAwait(deviceId); sendObserveCancelAllWithAwait(deviceId);
// ObserveComposite // ObserveComposite
sendCancelObserveAllWithAwait(deviceId);
String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3;
String expectedIdVer19_1_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "/" + RESOURCE_INSTANCE_ID_0; String expectedIdVer19_1_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "/" + RESOURCE_INSTANCE_ID_0;
String expectedIds = "[\"" + objectIdVer_3 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + expectedIdVer19_1_0_0 + "\"]"; String expectedIds = "[\"" + objectIdVer_3 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + expectedIdVer19_1_0_0 + "\"]";
String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
// ObserveCompositeCancel // ObserveCompositeCancel
expectedIds = "[\"" + expectedIdVer19_1_0_0 + "\", \"" + idVer_3_0_9 + "\"]"; String sendIds = "[\"" + expectedIdVer19_1_0_0 + "\", \"" + idVer_3_0_9 + "\"]";
actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds); actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", sendIds);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expectedValue = "for observation path [" + fromVersionedIdToObjectId(objectIdVer_3) + "], that includes this observation path [" + fromVersionedIdToObjectId(idVer_3_0_9); String expectedValue = "Could not find active Observe Composite component with paths: [/19_1.1/1/0/0, /3_1.2/0/9]";
assertTrue(rpcActualResult.get("error").asText().contains(expectedValue)); assertTrue(rpcActualResult.get("error").asText().contains(expectedValue));
// ObserveCompositeCancel // "ObserveReadAll"
expectedIds = "[\"" + objectIdVer_3 + "\", \"" + expectedIdVer19_1_0_0 + "\"]";
actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertEquals("2", rpcActualResult.get("value").asText());
String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null); String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null);
ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText());
String actualValues = rpcActualResultReadAll.get("value").asText(); String actualValues = rpcActualResultReadAll.get("value").asText();
assertEquals("[\"SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer5_0_3) + "\"]", actualValues); assertTrue(actualValues.contains("CompositeObservation:"));
} }
/**
* ObserveComposite {"ids":["/3/0/9", "/3/0/14", "/5/0/3", "/3/0/15", "/19/1/0/0"]} - Ok
* ObserveCancel {"id":"/3/0/9"} -> INTERNAL_SERVER_ERROR
* ObserveCompositeCancel {"ids":["/3/0/9", "/19/1/0/0", "/3]} - Ok
* last Observation
* @throws Exception
*/
@Test @Test
public void testObserveCompositeAnyResources_Result_CONTENT_CancelObserveComposite_OneResource_OneObjectAnyResource_Result_Content_Count_4() throws Exception { public void testObserveCompositeResource_Update_After_Registration_UpdateRegistration() throws Exception {
sendCancelObserveAllWithAwait(deviceId); String id_3_0_9 = fromVersionedIdToObjectId(idVer_3_0_9);
// ObserveComposite String id_19_0_0 = fromVersionedIdToObjectId(idVer_19_0_0);
sendCancelObserveAllWithAwait(deviceId); String idVer_19_1_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0;
String expectedIdVer3_0_14 = objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_14; String id_19_1_0 = fromVersionedIdToObjectId(idVer_19_1_0);
String expectedIdVer3_0_15= objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_15; String idVer_19_0_2 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_2;
String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; String id_19_0_2 = fromVersionedIdToObjectId(idVer_19_0_2);
String expectedIdVer19_1_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "/" + RESOURCE_INSTANCE_ID_0;
String expectedIds = "[\"" + idVer_3_0_9 + "\", \"" + expectedIdVer3_0_14 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + expectedIdVer3_0_15 + "\", \"" + expectedIdVer19_1_0_0 + "\"]"; // 1 - "ObserveReadAll": at least one update value of all resources we observe - after connection
String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
// ObserveCompositeCancel
expectedIds = "[\"" + idVer_3_0_9 + "\", \"" + expectedIdVer19_1_0_0 + "\", \"" + objectIdVer_3 + "\"]";
actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertEquals("4", rpcActualResult.get("value").asText());
String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null); String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null);
ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText());
String actualValues = rpcActualResultReadAll.get("value").asText(); String rpcActualVValuesReadAll = rpcActualResultReadAll.get("value").asText();
assertEquals("[\"SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer5_0_3) + "\"]", actualValues); ArrayNode rpcactualValues = JacksonUtil.fromString(rpcActualVValuesReadAll, ArrayNode.class);
assertEquals(rpcactualValues.size(), 4);
assertTrue(actualResultReadAll.contains("SingleObservation:" + id_3_0_9));
assertTrue(actualResultReadAll.contains("SingleObservation:" + id_19_1_0));
assertTrue(actualResultReadAll.contains("SingleObservation:" + id_19_0_2));
assertTrue(actualResultReadAll.contains("SingleObservation:" + id_19_0_0));
long initAttrTelemetryAtCount = countUpdateAttrTelemetryAll();
long initAttrTelemetryAtCount_3_0_9 = countUpdateAttrTelemetryResource(idVer_3_0_9);
long initAttrTelemetryAtCount_19_0_0 = countUpdateAttrTelemetryResource(idVer_19_0_0);
long initAttrTelemetryAtCount_19_1_0 = countUpdateAttrTelemetryResource(idVer_19_1_0);
long initAttrTelemetryAtCount_19_0_2 = countUpdateAttrTelemetryResource(idVer_19_0_2);
updateRegAtLeastOnceAfterAction();
updateAttrTelemetryAllAtLeastOnceAfterAction(initAttrTelemetryAtCount);
updateAttrTelemetryResourceAtLeastOnceAfterAction(initAttrTelemetryAtCount_3_0_9, idVer_3_0_9);
updateAttrTelemetryResourceAtLeastOnceAfterAction(initAttrTelemetryAtCount_19_0_0, idVer_19_0_0);
updateAttrTelemetryResourceAtLeastOnceAfterAction(initAttrTelemetryAtCount_19_1_0, idVer_19_1_0);
updateAttrTelemetryResourceAtLeastOnceAfterAction(initAttrTelemetryAtCount_19_0_2, idVer_19_0_2);
// 2 - "ObserveReadAll": No update of all resources we are observing - after "ObserveReadCancelAll"
sendObserveCancelAllWithAwait(deviceId);
updateRegAtLeastOnceAfterAction();
actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null);
rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText());
rpcActualVValuesReadAll = rpcActualResultReadAll.get("value").asText();
rpcactualValues = JacksonUtil.fromString(rpcActualVValuesReadAll, ArrayNode.class);
assertEquals(rpcactualValues.size(), 0);
// 2.1 - ObserveComposite: observeCancelAll verify"
initAttrTelemetryAtCount = countUpdateAttrTelemetryAll();
initAttrTelemetryAtCount_3_0_9 = countUpdateAttrTelemetryResource(idVer_3_0_9);
initAttrTelemetryAtCount_19_0_0 = countUpdateAttrTelemetryResource(idVer_19_0_0);
initAttrTelemetryAtCount_19_1_0 = countUpdateAttrTelemetryResource(idVer_19_1_0);
initAttrTelemetryAtCount_19_0_2 = countUpdateAttrTelemetryResource(idVer_19_0_2);
updateRegAtLeastOnceAfterAction();
assertEquals(countUpdateAttrTelemetryAll(), initAttrTelemetryAtCount);
assertEquals(countUpdateAttrTelemetryResource(idVer_3_0_9), initAttrTelemetryAtCount_3_0_9);
assertEquals(countUpdateAttrTelemetryResource(idVer_19_0_0), initAttrTelemetryAtCount_19_0_0);
assertEquals(countUpdateAttrTelemetryResource(idVer_19_1_0), initAttrTelemetryAtCount_19_1_0);
assertEquals(countUpdateAttrTelemetryResource(idVer_19_0_2), initAttrTelemetryAtCount_19_0_2);
// 3 - ObserveComposite: at least one update value of all resources we observe - after ObserveComposite"
String expectedKeys = "[\"" + RESOURCE_ID_NAME_3_9 + "\", \"" + RESOURCE_ID_NAME_19_0_0 + "\", \"" + RESOURCE_ID_NAME_19_0_2 + "\", \"" + RESOURCE_ID_NAME_19_1_0 + "\"]";
String actualResult = sendCompositeRPCByKeys("ObserveComposite", expectedKeys);
assertTrue(actualResult.contains(id_3_0_9 + "=LwM2mSingleResource"));
assertTrue(actualResult.contains(id_19_0_0 + "=LwM2mMultipleResource"));
assertTrue(actualResult.contains(id_19_1_0 + "=LwM2mMultipleResource"));
assertTrue(actualResult.contains(id_19_0_2 + "=LwM2mSingleResource"));
// 3.1 - ObserveComposite: - verify");
actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null);
rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText());
rpcActualVValuesReadAll = rpcActualResultReadAll.get("value").asText();
rpcactualValues = JacksonUtil.fromString(rpcActualVValuesReadAll, ArrayNode.class);
assertEquals(rpcactualValues.size(), 1);
assertFalse(actualResultReadAll.contains("SingleObservation"));
assertTrue(actualResultReadAll.contains("CompositeObservation:"));
assertTrue(actualResultReadAll.contains(id_19_0_2));
assertTrue(actualResultReadAll.contains(id_19_1_0));
assertTrue(actualResultReadAll.contains(id_19_0_0));
assertTrue(actualResultReadAll.contains(id_3_0_9));
initAttrTelemetryAtCount = countUpdateAttrTelemetryAll();
initAttrTelemetryAtCount_3_0_9 = countUpdateAttrTelemetryResource(idVer_3_0_9);
initAttrTelemetryAtCount_19_0_0 = countUpdateAttrTelemetryResource(idVer_19_0_0);
initAttrTelemetryAtCount_19_1_0 = countUpdateAttrTelemetryResource(idVer_19_1_0);
initAttrTelemetryAtCount_19_0_2 = countUpdateAttrTelemetryResource(idVer_19_0_2);
updateRegAtLeastOnceAfterAction();
updateAttrTelemetryAllAtLeastOnceAfterAction(initAttrTelemetryAtCount);
updateAttrTelemetryResourceAtLeastOnceAfterAction(initAttrTelemetryAtCount_3_0_9, idVer_3_0_9);
updateAttrTelemetryResourceAtLeastOnceAfterAction(initAttrTelemetryAtCount_19_0_0, idVer_19_0_0);
updateAttrTelemetryResourceAtLeastOnceAfterAction(initAttrTelemetryAtCount_19_1_0, idVer_19_1_0);
updateAttrTelemetryResourceAtLeastOnceAfterAction(initAttrTelemetryAtCount_19_0_2, idVer_19_0_2);
} }
private String sendObserve(String method, String params) throws Exception { private String sendObserve(String method, String params) throws Exception {
String sendRpcRequest; String sendRpcRequest;
if (params == null) { if (params == null) {
sendRpcRequest = "{\"method\": \"" + method + "\"}"; sendRpcRequest = "{\"method\": \"" + method + "\"}";
} } else {
else {
sendRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"id\": \"" + params + "\"}}"; sendRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"id\": \"" + params + "\"}}";
} }
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, sendRpcRequest, String.class, status().isOk()); return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, sendRpcRequest, String.class, status().isOk());
@ -481,7 +513,71 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt
} }
private String sendCompositeRPCByKeys(String method, String keys) throws Exception { private String sendCompositeRPCByKeys(String method, String keys) throws Exception {
String setRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"keys\":" + keys + "}}"; String sendRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"keys\":" + keys + "}}";
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, sendRpcRequest, String.class, status().isOk());
}
private long countUpdateAttrTelemetryAll() {
return Mockito.mockingDetails(defaultUplinkMsgHandlerTest)
.getInvocations().stream()
.filter(invocation -> invocation.getMethod().getName().equals("updateAttrTelemetry"))
.count();
}
private void updateAttrTelemetryAllAtLeastOnceAfterAction(long initialInvocationCount) {
AtomicLong newInvocationCount = new AtomicLong(initialInvocationCount);
log.warn("countUpdateAttrTelemetryAllAtLeastOnceAfterAction: initialInvocationCount [{}]", initialInvocationCount);
await("Update AttrTelemetryAll at-least-once after action")
.atMost(50, TimeUnit.SECONDS)
.until(() -> {
newInvocationCount.set(countUpdateAttrTelemetryAll());
return newInvocationCount.get() > initialInvocationCount;
});
log.warn("countUpdateAttrTelemetryAllAtLeastOnceAfterAction: newInvocationCount [{}]", newInvocationCount.get());
}
private long countUpdateAttrTelemetryResource(String idVerRez) {
return Mockito.mockingDetails(defaultUplinkMsgHandlerTest)
.getInvocations().stream()
.filter(invocation ->
invocation.getMethod().getName().equals("updateAttrTelemetry") &&
invocation.getArguments().length > 1 &&
idVerRez.equals(invocation.getArguments()[1])
)
.count();
}
private void updateAttrTelemetryResourceAtLeastOnceAfterAction(long initialInvocationCount, String idVerRez) {
AtomicLong newInvocationCount = new AtomicLong(initialInvocationCount);
log.warn("countUpdateAttrTelemetryResourceAtLeastOnceAfterAction: initialInvocationCount [{}]", initialInvocationCount);
await("Update AttrTelemetryResource at-least-once after action")
.atMost(50, TimeUnit.SECONDS)
.until(() -> {
newInvocationCount.set(countUpdateAttrTelemetryResource(idVerRez));
return newInvocationCount.get() > initialInvocationCount;
});
log.warn("countUpdateAttrTelemetryResourceAtLeastOnceAfterAction: newInvocationCount [{}]", newInvocationCount.get());
}
private long countUpdateReg() {
return Mockito.mockingDetails(defaultUplinkMsgHandlerTest)
.getInvocations().stream()
.filter(invocation -> invocation.getMethod().getName().equals("updatedReg"))
.count();
}
private void updateRegAtLeastOnceAfterAction() {
long initialInvocationCount = countUpdateReg();
AtomicLong newInvocationCount = new AtomicLong(initialInvocationCount);
log.warn("updateRegAtLeastOnceAfterAction: initialInvocationCount [{}]", initialInvocationCount);
await("Update Registration at-least-once after action")
.atMost(50, TimeUnit.SECONDS)
.until(() -> {
newInvocationCount.set(countUpdateReg());
return newInvocationCount.get() > initialInvocationCount;
});
log.warn("updateRegAtLeastOnceAfterAction: newInvocationCount [{}]", newInvocationCount.get());
} }
} }

20
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java

@ -23,6 +23,8 @@ import org.eclipse.leshan.core.link.LinkParseException;
import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mPath;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.transport.lwm2m.config.TbLwM2mVersion;
import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest;
import java.util.Arrays; import java.util.Arrays;
@ -33,9 +35,10 @@ import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_2; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_2;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.convertObjectIdToVerId;
public class RpcLwm2mIntegrationDiscoverTest extends AbstractRpcLwM2MIntegrationTest { public class RpcLwm2mIntegrationDiscoverTest extends AbstractRpcLwM2MIntegrationTest {
@ -172,4 +175,19 @@ public class RpcLwm2mIntegrationDiscoverTest extends AbstractRpcLwM2MIntegration
String setRpcRequest = "{\"method\": \"Discover\", \"params\": {\"id\": \"" + path + "\"}}"; String setRpcRequest = "{\"method\": \"Discover\", \"params\": {\"id\": \"" + path + "\"}}";
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk());
} }
private String convertObjectIdToVerId(String path, String ver) {
ver = ver != null ? ver : TbLwM2mVersion.VERSION_1_0.getVersion().toString();
try {
String[] keyArray = path.split(LWM2M_SEPARATOR_PATH);
if (keyArray.length > 1) {
keyArray[1] = keyArray[1] + LWM2M_SEPARATOR_KEY + ver;
return StringUtils.join(keyArray, LWM2M_SEPARATOR_PATH);
} else {
return path;
}
} catch (Exception e) {
return null;
}
}
} }

315
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java

@ -25,7 +25,6 @@ import org.eclipse.leshan.server.registration.Registration;
import org.junit.Test; import org.junit.Test;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.boot.test.mock.mockito.SpyBean;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationObserveTest; import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationObserveTest;
import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2mUplinkMsgHandler;
@ -38,10 +37,10 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_1;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_2; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_2;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_9;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId; import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId;
@Slf4j @Slf4j
@ -51,16 +50,12 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO
DefaultLwM2mUplinkMsgHandler defaultUplinkMsgHandlerTest; DefaultLwM2mUplinkMsgHandler defaultUplinkMsgHandlerTest;
@Test @Test
public void testObserveReadAll_Count_2_CancelAll_Count_0_Ok() throws Exception { public void testObserveReadAll_Count_4_CancelAll_Count_0_Ok() throws Exception {
String actualResultReadAll = sendRpcObserve("ObserveReadAll", null); String actualValuesReadAll = sendRpcObserveOkWithResultValue("ObserveReadAll", null);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); assertEquals(4, actualValuesReadAll.split(",").length);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); sendObserveCancelAllWithAwait(deviceId);
String actualValuesReadAll = rpcActualResult.get("value").asText(); actualValuesReadAll = sendRpcObserveOkWithResultValue("ObserveReadAll", null);
assertEquals(2, actualValuesReadAll.split(",").length); assertEquals("[]", actualValuesReadAll);
String expected = "\"SingleObservation:/19/0/0\"";
assertTrue(actualValuesReadAll.contains(expected));
expected = "\"SingleObservation:/3/0/9\"";
assertTrue(actualValuesReadAll.contains(expected));
} }
/** /**
@ -69,16 +64,11 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO
*/ */
@Test @Test
public void testObserveOneResource_Result_CONTENT_Value_Count_3_After_Cancel_Count_2() throws Exception { public void testObserveOneResource_Result_CONTENT_Value_Count_3_After_Cancel_Count_2() throws Exception {
sendCancelObserveAllWithAwait(deviceId); sendObserveCancelAllWithAwait(deviceId);
String idVer_3_0_9 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9; sendRpcObserveWithContainsLwM2mSingleResource(idVer_3_0_9);
String actualResult = sendRpcObserve("Observe", idVer_3_0_9);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertTrue(rpcActualResult.get("value").asText().contains("LwM2mSingleResource"));
assertEquals(Optional.of(1).get(), Optional.ofNullable(getCntObserveAll(deviceId)).get());
int cntUpdate = 3; int cntUpdate = 3;
verify(defaultUplinkMsgHandlerTest, timeout(10000).times(cntUpdate)) verify(defaultUplinkMsgHandlerTest, timeout(10000).times(cntUpdate))
.onUpdateValueAfterReadResponse(Mockito.any(Registration.class), eq(idVer_3_0_9), Mockito.any(ReadResponse.class)); .onUpdateValueAfterReadResponse(Mockito.any(Registration.class), eq(idVer_3_0_9), Mockito.any(ReadResponse.class));
} }
@ -88,15 +78,12 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO
*/ */
@Test @Test
public void testObserveOneObjectInstance_Result_CONTENT_Value_Count_3_After_Cancel_Count_2() throws Exception { public void testObserveOneObjectInstance_Result_CONTENT_Value_Count_3_After_Cancel_Count_2() throws Exception {
sendCancelObserveAllWithAwait(deviceId); sendObserveCancelAllWithAwait(deviceId);
String idVer_3_0 = objectInstanceIdVer_3; String idVer_3_0 = objectInstanceIdVer_3;
String actualResult = sendRpcObserve("Observe", idVer_3_0); sendRpcObserveWithContainsLwM2mSingleResource(idVer_3_0);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertTrue(rpcActualResult.get("value").asText().contains("LwM2mSingleResource"));
assertEquals(Optional.of(1).get(), Optional.ofNullable(getCntObserveAll(deviceId)).get());
int cntUpdate = 3; int cntUpdate = 3;
verify(defaultUplinkMsgHandlerTest, timeout(10000).times(cntUpdate)) verify(defaultUplinkMsgHandlerTest, timeout(10000).times(cntUpdate))
.updateAttrTelemetry(Mockito.any(Registration.class), eq(idVer_3_0_9)); .updateAttrTelemetry(Mockito.any(Registration.class), eq(idVer_3_0_9));
} }
@ -106,19 +93,15 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO
*/ */
@Test @Test
public void testObserveOneObject_Result_CONTENT_Value_Count_3_After_Cancel_Count_2() throws Exception { public void testObserveOneObject_Result_CONTENT_Value_Count_3_After_Cancel_Count_2() throws Exception {
sendCancelObserveAllWithAwait(deviceId); sendObserveCancelAllWithAwait(deviceId);
String idVer_3_0 = objectInstanceIdVer_3; String idVer_3_0 = objectInstanceIdVer_3;
String actualResult = sendRpcObserve("Observe", idVer_3_0); sendRpcObserveWithContainsLwM2mSingleResource(idVer_3_0);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertTrue(rpcActualResult.get("value").asText().contains("LwM2mSingleResource"));
assertEquals(Optional.of(1).get(), Optional.ofNullable(getCntObserveAll(deviceId)).get());
int cntUpdate = 3; int cntUpdate = 3;
verify(defaultUplinkMsgHandlerTest, timeout(10000).times(cntUpdate)) verify(defaultUplinkMsgHandlerTest, timeout(10000).times(cntUpdate))
.updateAttrTelemetry(Mockito.any(Registration.class), eq(idVer_3_0_9)); .updateAttrTelemetry(Mockito.any(Registration.class), eq(idVer_3_0_9));
} }
/** /**
* Repeated request on Observe * Repeated request on Observe
* Observe {"id":"/3_1.2/0/0"} * Observe {"id":"/3_1.2/0/0"}
@ -126,15 +109,10 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO
*/ */
@Test @Test
public void testObserveRepeated_Result_CONTENT_AddIfAbsent() throws Exception { public void testObserveRepeated_Result_CONTENT_AddIfAbsent() throws Exception {
String idVer_3_0_0 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0; sendRpcObserveOkWithResultValue("Observe", idVer_3_0_0);
String actualResult = sendRpcObserve("Observe", idVer_3_0_0); String rpcActualResult = sendRpcObserveOkWithResultValue("Observe", idVer_3_0_0);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
actualResult = sendRpcObserve("Observe", idVer_3_0_0);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String expected = "LwM2mSingleResource [id=0"; String expected = "LwM2mSingleResource [id=0";
assertTrue(rpcActualResult.get("value").asText().contains(expected)); assertTrue(rpcActualResult.contains(expected));
} }
/** /**
@ -143,15 +121,14 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO
*/ */
@Test @Test
public void testObserveWithBadVersion_Result_BadRequest_ErrorMsg_BadVersionMustBe_Ver() throws Exception { public void testObserveWithBadVersion_Result_BadRequest_ErrorMsg_BadVersionMustBe_Ver() throws Exception {
String expectedInstance = (String) expectedInstances.stream().filter(path -> !((String)path).contains("_")).findFirst().get(); String expectedInstance = (String) expectedInstances.stream().filter(path -> !((String) path).contains("_")).findFirst().get();
LwM2mPath expectedPath = new LwM2mPath(expectedInstance); LwM2mPath expectedPath = new LwM2mPath(expectedInstance);
int expectedResource = lwM2MTestClient.getLeshanClient().getObjectTree().getObjectEnablers().get(expectedPath.getObjectId()).getObjectModel().resources.entrySet().stream().findAny().get().getKey(); int expectedResource = lwM2MTestClient.getLeshanClient().getObjectTree().getObjectEnablers().get(expectedPath.getObjectId()).getObjectModel().resources.entrySet().stream().findAny().get().getKey();
String ver = lwM2MTestClient.getLeshanClient().getObjectTree().getObjectEnablers().get(expectedPath.getObjectId()).getObjectModel().version; String ver = lwM2MTestClient.getLeshanClient().getObjectTree().getObjectEnablers().get(expectedPath.getObjectId()).getObjectModel().version;
String expectedId = "/" + expectedPath.getObjectId() + "_" + Version.MAX + "/" + expectedPath.getObjectInstanceId() + "/" + expectedResource; String expectedId = "/" + expectedPath.getObjectId() + "_" + Version.MAX + "/" + expectedPath.getObjectInstanceId() + "/" + expectedResource;
String actualResult = sendRpcObserve("Observe", expectedId); ObjectNode rpcActualResult = sendRpcObserveWithResult("Observe", expectedId);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expected = "Specified resource id " + expectedId +" is not valid version! Must be version: " + ver; String expected = "Specified resource id " + expectedId + " is not valid version! Must be version: " + ver;
assertEquals(expected, rpcActualResult.get("error").asText()); assertEquals(expected, rpcActualResult.get("error").asText());
} }
@ -162,10 +139,9 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO
*/ */
@Test @Test
public void testObserveNoImplementedInstanceOnDevice_Result_NotFound() throws Exception { public void testObserveNoImplementedInstanceOnDevice_Result_NotFound() throws Exception {
String objectInstanceIdVer = (String) expectedObjectIdVers.stream().filter(path -> ((String)path).contains("/" + ACCESS_CONTROL)).findFirst().get(); String objectInstanceIdVer = (String) expectedObjectIdVers.stream().filter(path -> ((String) path).contains("/" + ACCESS_CONTROL)).findFirst().get();
String expected = objectInstanceIdVer + "/" + OBJECT_INSTANCE_ID_0; String expected = objectInstanceIdVer + "/" + OBJECT_INSTANCE_ID_0;
String actualResult = sendRpcObserve("Observe", expected); ObjectNode rpcActualResult = sendRpcObserveWithResult("Observe", expected);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.NOT_FOUND.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.NOT_FOUND.getName(), rpcActualResult.get("result").asText());
} }
@ -177,8 +153,7 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO
@Test @Test
public void testObserveNoImplementedResourceOnDeviceValueNull_Result_BadRequest() throws Exception { public void testObserveNoImplementedResourceOnDeviceValueNull_Result_BadRequest() throws Exception {
String expected = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_3; String expected = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_3;
String actualResult = sendRpcObserve("Observe", expected); ObjectNode rpcActualResult = sendRpcObserveWithResult("Observe", expected);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
String expectedValue = "value MUST NOT be null"; String expectedValue = "value MUST NOT be null";
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
assertEquals(expectedValue, rpcActualResult.get("error").asText()); assertEquals(expectedValue, rpcActualResult.get("error").asText());
@ -191,9 +166,7 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO
@Test @Test
public void testObserveResourceNotRead_Result_METHOD_NOT_ALLOWED() throws Exception { public void testObserveResourceNotRead_Result_METHOD_NOT_ALLOWED() throws Exception {
String expectedId = objectInstanceIdVer_5 + "/" + RESOURCE_ID_0; String expectedId = objectInstanceIdVer_5 + "/" + RESOURCE_ID_0;
sendRpcObserve("Observe", expectedId); ObjectNode rpcActualResult = sendRpcObserveWithResult("Observe", expectedId);
String actualResult = sendRpcObserve("Observe", expectedId);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.METHOD_NOT_ALLOWED.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.METHOD_NOT_ALLOWED.getName(), rpcActualResult.get("result").asText());
} }
@ -204,9 +177,7 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO
@Test @Test
public void testObserveExecuteResource_Result_METHOD_NOT_ALLOWED() throws Exception { public void testObserveExecuteResource_Result_METHOD_NOT_ALLOWED() throws Exception {
String expectedId = objectInstanceIdVer_5 + "/" + RESOURCE_ID_2; String expectedId = objectInstanceIdVer_5 + "/" + RESOURCE_ID_2;
sendRpcObserve("Observe", expectedId); ObjectNode rpcActual = sendRpcObserveWithResult("Observe", expectedId);
String actual = sendRpcObserve("Observe", expectedId);
ObjectNode rpcActual = JacksonUtil.fromString(actual, ObjectNode.class);
assertEquals(ResponseCode.METHOD_NOT_ALLOWED.getName(), rpcActual.get("result").asText()); assertEquals(ResponseCode.METHOD_NOT_ALLOWED.getName(), rpcActual.get("result").asText());
} }
@ -217,100 +188,80 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO
*/ */
@Test @Test
public void testObserveRepeatedRequestObserveOnDevice_Result_CONTENT_PutIfAbsent() throws Exception { public void testObserveRepeatedRequestObserveOnDevice_Result_CONTENT_PutIfAbsent() throws Exception {
String idVer_3_0_0 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0; sendRpcObserveOkWithResultValue("Observe", idVer_3_0_0);
String actualResult = sendRpcObserve("Observe", idVer_3_0_0); String rpcActualResult = sendRpcObserveOkWithResultValue("Observe", idVer_3_0_0);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
actualResult = sendRpcObserve("Observe", idVer_3_0_0);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String expected = "LwM2mSingleResource [id=0"; String expected = "LwM2mSingleResource [id=0";
assertTrue(rpcActualResult.get("value").asText().contains(expected)); assertTrue(rpcActualResult.contains(expected));
} }
/** /**
* Observe {"id":["3"]} - Ok
* PreviousObservation contains "3/0/9" * PreviousObservation contains "3/0/9"
* Observe {"id":["19"]} - Bad Request
* Observe {"id":["19/0"]} - Bad Request
* Observe {"id":["19/1"]} - Ok
* @throws Exception * @throws Exception
*/ */
@Test @Test
public void testObserve_Result_CONTENT_ONE_PATH_PreviousObservation_CONTAINCE_OTHER_CurrentObservation() throws Exception { public void testObserves_OverlappedPaths_FirstResource_SecondObjectOrInstance() throws Exception {
sendCancelObserveAllWithAwait(deviceId); sendObserveCancelAllWithAwait(deviceId);
// "3/0/9" // "19/0/0"
String idVer_3_0_9 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9; sendRpcObserveOkWithResultValue("Observe", idVer_19_0_0);
String actualResult3_0_9 = sendRpcObserve("Observe", idVer_3_0_9); // PreviousObservation "19/0/0" change to CurrentObservation "19" - object
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult3_0_9, ObjectNode.class); ObjectNode rpcActualResult = sendRpcObserveWithResult("Observe", objectIdVer_19);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
// "3" String expected = "Resource [" + fromVersionedIdToObjectId(objectIdVer_19) + "] conflict with is already registered as SingleObservation [" + fromVersionedIdToObjectId(idVer_19_0_0) + "].";
String actualResult3 = sendRpcObserve("Observe", objectIdVer_3); assertEquals(expected, rpcActualResult.get("error").asText());
rpcActualResult = JacksonUtil.fromString(actualResult3, ObjectNode.class); // Verify ObserveReadAll
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); String actualValuesReadAll = sendRpcObserveOkWithResultValue("ObserveReadAll", null);
// PreviousObservation "3/0/9" change to CurrentObservation "3" String expectedReadAll = "[\"SingleObservation:/19/0/0\"]";
String actualResultReadAll = sendRpcObserve("ObserveReadAll", null); assertEquals(expectedReadAll, actualValuesReadAll);
rpcActualResult = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); // PreviousObservation "19/0/0" change to CurrentObservation "19/0" - instance
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); String expectedIdVer19_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0;
String actualValuesReadAll = rpcActualResult.get("value").asText(); rpcActualResult = sendRpcObserveWithResult("Observe", expectedIdVer19_0);
assertEquals(1, actualValuesReadAll.split(",").length); assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expected = "\"SingleObservation:/3\""; expected = "Resource [" + fromVersionedIdToObjectId(expectedIdVer19_0) + "] conflict with is already registered as SingleObservation [" + fromVersionedIdToObjectId(idVer_19_0_0) + "].";
assertTrue(actualValuesReadAll.contains(expected)); assertEquals(expected, rpcActualResult.get("error").asText());
} // Verify ObserveReadAll
actualValuesReadAll = sendRpcObserveOkWithResultValue("ObserveReadAll", null);
/** assertEquals(expectedReadAll, actualValuesReadAll);
* Observe {"id":["3/0/9"]} - Ok // PreviousObservation "19/0/0" add CurrentObservation "19/1" - instance
* PreviousObservation contains "3" String expectedIdVer19_1 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1;
* @throws Exception rpcActualResult = sendRpcObserveWithResult("Observe", expectedIdVer19_1);
*/
@Test
public void testObserve_Result_CONTENT_ONE_PATH_CurrentObservation_CONTAINCE_OTHER_PreviousObservation() throws Exception {
sendCancelObserveAllWithAwait(deviceId);
// "3"
String actualResult3 = sendRpcObserve("Observe", objectIdVer_3);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult3, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
// "3/0/0"; WARN: - Token collision ? existing observation [/3] includes input observation [/3/0/0]
String idVer_3_0_0 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0;
String actualResult3_0_0 = sendRpcObserve("Observe", idVer_3_0_0);
rpcActualResult = JacksonUtil.fromString(actualResult3_0_0, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String actualResultReadAll = sendRpcObserve("ObserveReadAll", null);
rpcActualResult = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String actualValuesReadAll = rpcActualResult.get("value").asText(); assertTrue(rpcActualResult.get("value").asText().contains("LwM2mObjectInstance"));
assertEquals(1, actualValuesReadAll.split(",").length); // Verify ObserveReadAll
String expected = "\"SingleObservation:/3\""; actualValuesReadAll = sendRpcObserveOkWithResultValue("ObserveReadAll", null);
assertTrue(actualValuesReadAll.contains(expected)); assertTrue(actualValuesReadAll.contains("SingleObservation:/19/1"));
assertTrue(actualValuesReadAll.contains("SingleObservation:/19/0/0"));
// PreviousObservation "19/1/"- instance change to CurrentObservation "19/1/0" - resource
String expectedIdVer19_1_0 = expectedIdVer19_1 + "/" + RESOURCE_ID_0;
rpcActualResult = sendRpcObserveWithResult("Observe", expectedIdVer19_1_0);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
expected = "Resource [" + fromVersionedIdToObjectId(expectedIdVer19_1_0) + "] conflict with is already registered as SingleObservation [" + fromVersionedIdToObjectId(expectedIdVer19_1) + "].";
assertEquals(expected, rpcActualResult.get("error").asText());
// Verify ObserveReadAll
actualValuesReadAll = sendRpcObserveOkWithResultValue("ObserveReadAll", null);
assertTrue(actualValuesReadAll.contains("SingleObservation:/19/1"));
assertTrue(actualValuesReadAll.contains("SingleObservation:/19/0/0"));
} }
/** /**
* Observe {"id":"/3/0/9"} * Observe {"id":"/3/0/9"}
* ObserveCancel {"id":"/3/0/9"} * ObserveCancel {"id":"/3/0/9"}
*/ */
@Test @Test
public void testObserveResource_ObserveCancelResource_Result_CONTENT_Count_1() throws Exception { public void testObserveResource_ObserveCancelResource_Result_CONTENT_Count_1() throws Exception {
sendCancelObserveAllWithAwait(deviceId); sendObserveCancelAllWithAwait(deviceId);
String expectedId_3_0_9 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9; String actualValuesReadAll = sendRpcObserveReadAllWithResult(idVer_3_0_9);
sendRpcObserve("Observe", expectedId_3_0_9);
String actualResultReadAll = sendRpcObserve("ObserveReadAll", null);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String actualValuesReadAll = rpcActualResult.get("value").asText();
assertEquals(1, actualValuesReadAll.split(",").length); assertEquals(1, actualValuesReadAll.split(",").length);
String expected = "\"SingleObservation:" + fromVersionedIdToObjectId(expectedId_3_0_9) + "\""; String expected = "\"SingleObservation:" + id_3_0_9 + "\"";
assertTrue(actualValuesReadAll.contains(expected)); assertTrue(actualValuesReadAll.contains(expected));
// cancel observe "/3_1.2/0/9" // cancel observe "/3_1.2/0/9"
String actualResult = sendRpcObserve("ObserveCancel", expectedId_3_0_9); sendRpcObserveOkWithResultValue("ObserveCancel", idVer_3_0_9);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertEquals("1", rpcActualResult.get("value").asText());
} }
/** /**
* Observe {"id":"/3"} * Observe {"id":"/3"}
* ObserveCancel {"id":"/3/0/9"} -> INTERNAL_SERVER_ERROR * ObserveCancel {"id":"/3/0/9"} -> INTERNAL_SERVER_ERROR
@ -318,64 +269,90 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO
*/ */
@Test @Test
public void testObserveObject_ObserveCancelOneResource_Result_INTERNAL_SERVER_ERROR_Than_Cancel_ObserveObject_Result_CONTENT_Count_1() throws Exception { public void testObserveObject_ObserveCancelOneResource_Result_INTERNAL_SERVER_ERROR_Than_Cancel_ObserveObject_Result_CONTENT_Count_1() throws Exception {
sendCancelObserveAllWithAwait(deviceId); sendObserveCancelAllWithAwait(deviceId);
String expectedId_3 = objectIdVer_3; String actualValuesReadAll = sendRpcObserveReadAllWithResult(objectIdVer_3);
sendRpcObserve("Observe", expectedId_3);
String actualResultReadAll = sendRpcObserve("ObserveReadAll", null);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String actualValuesReadAll = rpcActualResult.get("value").asText();
assertEquals(1, actualValuesReadAll.split(",").length); assertEquals(1, actualValuesReadAll.split(",").length);
String expected = "\"SingleObservation:" + fromVersionedIdToObjectId(expectedId_3) + "\""; String expected = "\"SingleObservation:" + fromVersionedIdToObjectId(objectIdVer_3) + "\"";
assertTrue(actualValuesReadAll.contains(expected)); assertTrue(actualValuesReadAll.contains(expected));
// cancel observe "/3_1.2/0/9" // cancel observe "/3_1.2/0/9"
String expectedId_3_0_9 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9; ObjectNode rpcActualResult = sendRpcObserveWithResult("ObserveCancel", idVer_3_0_9);
String actualResult = sendRpcObserve("ObserveCancel", expectedId_3_0_9); String expectedValue = "Could not find active Observe component with path: " + idVer_3_0_9;
String expectedValue = "for observation path [" + fromVersionedIdToObjectId(objectIdVer_3) + "], that includes this observation path [" + fromVersionedIdToObjectId(expectedId_3_0_9);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
assertTrue(rpcActualResult.get("error").asText().contains(expectedValue)); assertTrue(rpcActualResult.get("error").asText().contains(expectedValue));
// cancel observe "/3_1.2" // cancel observe "/3_1.2"
actualResult = sendRpcObserve("ObserveCancel", expectedId_3); sendRpcObserveOkWithResultValue("ObserveCancel", objectIdVer_3);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertEquals("1", rpcActualResult.get("value").asText());
} }
/** /**
* Observe {"id":"/3/0/0"} * Observe {"id":"/3/0/0"}
* Observe {"id":"/3/0/9"} * Observe {"id":"/3/0/9"}
* ObserveCancel {"id":"/3"} - Ok, cnt = 2 * ObserveCancel {"id":"/3"} - Bad
* ObserveCancel {"/3/0/0"} - Ok
* ObserveCancel {"/3/0/9"} - Ok
*
*/ */
@Test @Test
public void testObserveResource_ObserveCancelObject_Result_CONTENT_Count_1() throws Exception { public void testObserveResource_ObserveCancelObject_Result_CONTENT_Count_1() throws Exception {
sendCancelObserveAllWithAwait(deviceId); sendObserveCancelAllWithAwait(deviceId);
String expectedId_3_0_0 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0; sendRpcObserveWithWithTwoResource(idVer_3_0_0, idVer_3_0_9);
sendRpcObserve("Observe", expectedId_3_0_0); String rpcActualResul = sendRpcObserveOkWithResultValue("ObserveReadAll", null);
String expectedId_3_0_9 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9; assertEquals(2, rpcActualResul.split(",").length);
sendRpcObserve("Observe", expectedId_3_0_9); String expected_3_0_0 = "\"SingleObservation:" + fromVersionedIdToObjectId(idVer_3_0_0) + "\"";
String actualResultReadAll = sendRpcObserve("ObserveReadAll", null); String expected_3_0_9 = "\"SingleObservation:" + id_3_0_9 + "\"";
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); assertTrue(rpcActualResul.contains(expected_3_0_0));
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); assertTrue(rpcActualResul.contains(expected_3_0_9));
String actualValuesReadAll = rpcActualResult.get("value").asText(); // cancel observe "/3_1.2"
assertEquals(2, actualValuesReadAll.split(",").length); ObjectNode rpcActualResult = sendRpcObserveWithResult("ObserveCancel", objectIdVer_3);
String expected_3_0_0 = "\"SingleObservation:" + fromVersionedIdToObjectId(expectedId_3_0_0) + "\""; assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expected_3_0_9 = "\"SingleObservation:" + fromVersionedIdToObjectId(expectedId_3_0_9) + "\""; String expected = "Could not find active Observe component with path: " + objectIdVer_3;
assertTrue(actualValuesReadAll.contains(expected_3_0_0)); assertEquals(expected, rpcActualResult.get("error").asText());
assertTrue(actualValuesReadAll.contains(expected_3_0_9)); // Verify ObserveReadAll
rpcActualResul = sendRpcObserveOkWithResultValue("ObserveReadAll", null);
// cancel observe "/3_1.2" String expectedReadAll = "[\"SingleObservation:/19/0/0\"]";
String expectedId_3 = objectIdVer_3; assertTrue(rpcActualResul.contains(expected_3_0_0));
String actualResult = sendRpcObserve("ObserveCancel", expectedId_3); assertTrue(rpcActualResul.contains(expected_3_0_9));
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); }
/**
* ObserveCancelAll
* Observe {"id":"3_1.2/0/9"}
* updateRegistration
* idResources_3_1.2/0/9 => updateAttrTelemetry >= 10 times
*/
@Test
public void testObserveResource_Update_AfterUpdateRegistration() throws Exception {
sendObserveCancelAllWithAwait(deviceId);
int cntUpdate = 3;
verify(defaultUplinkMsgHandlerTest, timeout(50000).atLeast(cntUpdate))
.updatedReg(Mockito.any(Registration.class));
sendRpcObserveWithContainsLwM2mSingleResource(idVer_3_0_9);
cntUpdate = 10;
verify(defaultUplinkMsgHandlerTest, timeout(50000).atLeast(cntUpdate))
.updateAttrTelemetry(Mockito.any(Registration.class), eq(idVer_3_0_9));
}
private void sendRpcObserveWithWithTwoResource(String expectedId_1, String expectedId_2) throws Exception {
sendRpcObserveOk("Observe", expectedId_1);
sendRpcObserveOk("Observe", expectedId_2);
}
private String sendRpcObserveReadAllWithResult(String params) throws Exception {
sendRpcObserveOk("Observe", params);
ObjectNode rpcActualResult = sendRpcObserveWithResult("ObserveReadAll", null);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertEquals("2", rpcActualResult.get("value").asText()); return rpcActualResult.get("value").asText();
} }
private String sendRpcObserve(String method, String params) throws Exception { private void sendRpcObserveWithContainsLwM2mSingleResource(String params) throws Exception {
return sendObserve(method, params, deviceId); String rpcActualResult = sendRpcObserveOkWithResultValue("Observe", params);
assertTrue(rpcActualResult.contains("LwM2mSingleResource"));
assertEquals(Optional.of(1).get(), Optional.ofNullable(getCntObserveAll(deviceId)).get());
} }
} }

2
common/actor/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.7.1-SNAPSHOT</version> <version>3.9.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/cache/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.7.1-SNAPSHOT</version> <version>3.9.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/cluster-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.7.1-SNAPSHOT</version> <version>3.9.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/coap-server/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.7.1-SNAPSHOT</version> <version>3.9.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/dao-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.7.1-SNAPSHOT</version> <version>3.9.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

3
common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java

@ -107,7 +107,7 @@ public interface AlarmService extends EntityDaoService {
PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId,
AlarmDataQuery query, Collection<EntityId> orderedEntityIds); AlarmDataQuery query, Collection<EntityId> orderedEntityIds);
PageData<AlarmId> findAlarmIdsByAssigneeId(TenantId tenantId, UserId userId, PageLink pageLink); List<TbPair<UUID, Long>> findAlarmIdsByAssigneeId(TenantId tenantId, UserId userId, long createdTimeOffset, AlarmId idOffset, int limit);
List<TbPair<UUID, Long>> findAlarmIdsByOriginatorId(TenantId tenantId, EntityId originatorId, long createdTimeOffset, AlarmId idOffset, int limit); List<TbPair<UUID, Long>> findAlarmIdsByOriginatorId(TenantId tenantId, EntityId originatorId, long createdTimeOffset, AlarmId idOffset, int limit);
@ -118,4 +118,5 @@ public interface AlarmService extends EntityDaoService {
long countAlarmsByQuery(TenantId tenantId, CustomerId customerId, AlarmCountQuery query); long countAlarmsByQuery(TenantId tenantId, CustomerId customerId, AlarmCountQuery query);
PageData<EntitySubtype> findAlarmTypesByTenantId(TenantId tenantId, PageLink pageLink); PageData<EntitySubtype> findAlarmTypesByTenantId(TenantId tenantId, PageLink pageLink);
} }

2
common/data/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.7.1-SNAPSHOT</version> <version>3.9.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

20
common/data/src/main/java/org/thingsboard/server/common/data/housekeeper/AlarmsUnassignHousekeeperTask.java

@ -21,6 +21,11 @@ import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.ToString; import lombok.ToString;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import java.util.List;
import java.util.UUID;
@Data @Data
@ToString(callSuper = true) @ToString(callSuper = true)
@ -29,10 +34,21 @@ import org.thingsboard.server.common.data.User;
public class AlarmsUnassignHousekeeperTask extends HousekeeperTask { public class AlarmsUnassignHousekeeperTask extends HousekeeperTask {
private String userTitle; private String userTitle;
private List<UUID> alarms;
protected AlarmsUnassignHousekeeperTask(User user) { protected AlarmsUnassignHousekeeperTask(User user) {
super(user.getTenantId(), user.getId(), HousekeeperTaskType.UNASSIGN_ALARMS); this(user.getTenantId(), user.getId(), user.getTitle(), null);
this.userTitle = user.getTitle(); }
public AlarmsUnassignHousekeeperTask(TenantId tenantId, UserId userId, String userTitle, List<UUID> alarms) {
super(tenantId, userId, HousekeeperTaskType.UNASSIGN_ALARMS);
this.userTitle = userTitle;
this.alarms = alarms;
}
@Override
public String getDescription() {
return super.getDescription() + (alarms != null ? " (" + alarms + ")" : "");
} }
} }

2
common/edge-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.7.1-SNAPSHOT</version> <version>3.9.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/message/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.7.1-SNAPSHOT</version> <version>3.9.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.7.1-SNAPSHOT</version> <version>3.9.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>common</artifactId> <artifactId>common</artifactId>

2
common/proto/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.7.1-SNAPSHOT</version> <version>3.9.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/queue/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.7.1-SNAPSHOT</version> <version>3.9.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/script/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.7.1-SNAPSHOT</version> <version>3.9.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/script/remote-js-client/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>3.7.1-SNAPSHOT</version> <version>3.9.0-SNAPSHOT</version>
<artifactId>script</artifactId> <artifactId>script</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.script</groupId> <groupId>org.thingsboard.common.script</groupId>

2
common/script/script-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>3.7.1-SNAPSHOT</version> <version>3.9.0-SNAPSHOT</version>
<artifactId>script</artifactId> <artifactId>script</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.script</groupId> <groupId>org.thingsboard.common.script</groupId>

2
common/stats/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.7.1-SNAPSHOT</version> <version>3.9.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/transport/coap/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>3.7.1-SNAPSHOT</version> <version>3.9.0-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.transport</groupId> <groupId>org.thingsboard.common.transport</groupId>

2
common/transport/http/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>3.7.1-SNAPSHOT</version> <version>3.9.0-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.transport</groupId> <groupId>org.thingsboard.common.transport</groupId>

2
common/transport/lwm2m/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>3.7.1-SNAPSHOT</version> <version>3.9.0-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.transport</groupId> <groupId>org.thingsboard.common.transport</groupId>

201
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java

@ -68,9 +68,8 @@ import org.eclipse.leshan.core.util.Hex;
import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.eclipse.leshan.server.model.LwM2mModelProvider;
import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.registration.Registration;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.device.profile.lwm2m.ObjectAttributes; import org.thingsboard.server.common.data.device.profile.lwm2m.ObjectAttributes;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext;
@ -88,14 +87,11 @@ import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionException;
import java.util.function.Function; import java.util.function.Function;
@ -177,24 +173,32 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
} }
} }
/**
* if resource in CompositeObservation is already registered - return BAD REQUEST
*/
@Override @Override
public void sendObserveRequest(LwM2mClient client, TbLwM2MObserveRequest request, DownlinkRequestCallback<ObserveRequest, ObserveResponse> callback) { public void sendObserveRequest(LwM2mClient client, TbLwM2MObserveRequest request, DownlinkRequestCallback<ObserveRequest, ObserveResponse> callback) {
try { try {
validateVersionedId(client, request); validateVersionedId(client, request);
LwM2mPath resultIds = new LwM2mPath(request.getObjectId()); LwM2mPath resultIds = new LwM2mPath(request.getObjectId());
ObserveRequest downlink; String resourceExisting = checkResourceSingleObservationForExisting(client, resultIds.toString());
ContentFormat contentFormat = getReadRequestContentFormat(client, request, modelProvider); if (StringUtils.isNotBlank(resourceExisting)) {
if (resultIds.isResourceInstance()) { callback.onValidationError(request.toString(), resourceExisting);
downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(), resultIds.getResourceInstanceId());
} else if (resultIds.isResource()) {
downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId());
} else if (resultIds.isObjectInstance()) {
downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId());
} else { } else {
downlink = new ObserveRequest(contentFormat, resultIds.getObjectId()); ObserveRequest downlink;
ContentFormat contentFormat = getReadRequestContentFormat(client, request, modelProvider);
if (resultIds.isResourceInstance()) {
downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(), resultIds.getResourceInstanceId());
} else if (resultIds.isResource()) {
downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId());
} else if (resultIds.isObjectInstance()) {
downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId());
} else {
downlink = new ObserveRequest(contentFormat, resultIds.getObjectId());
}
log.info("[{}] Send observation: {}.", client.getEndpoint(), request.getVersionedId());
sendSimpleRequest(client, downlink, request.getTimeout(), callback);
} }
log.info("[{}] Send observation: {}.", client.getEndpoint(), request.getVersionedId());
sendSimpleRequest(client, downlink, request.getTimeout(), callback);
} catch (InvalidRequestException e) { } catch (InvalidRequestException e) {
callback.onValidationError(request.toString(), e.getMessage()); callback.onValidationError(request.toString(), e.getMessage());
} }
@ -208,19 +212,7 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
if (observation instanceof SingleObservation) { if (observation instanceof SingleObservation) {
paths.add("SingleObservation:" + ((SingleObservation) observation).getPath().toString()); paths.add("SingleObservation:" + ((SingleObservation) observation).getPath().toString());
} else { } else {
List<LwM2mPath> listPath = ((CompositeObservation) observation).getPaths(); paths.add("CompositeObservation: " + ((CompositeObservation) observation).getPaths().toString());
List<String> pathsComposite = listPath.stream().map(lwM2mPath -> (lwM2mPath.toString())).collect(Collectors.toList());
Set <String> pathsCompositeSort = new TreeSet<>();
if (pathsComposite.size() == 1) {
pathsCompositeSort.add("CompositeObservation: [" + pathsComposite.get(0) + "]");
} else if (pathsComposite.size() > 1) {
List <String> sort = new LinkedList<>();
sort.addAll(pathsComposite);
sort.set(0, "CompositeObservation: [" + sort.get(0));
sort.set(pathsComposite.size()-1, sort.get(pathsComposite.size()-1) + "]");
pathsCompositeSort = new LinkedHashSet<>(sort);
}
paths.addAll(pathsCompositeSort);
} }
}); });
@ -234,10 +226,15 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
public void sendObserveCompositeRequest(LwM2mClient client, TbLwM2MObserveCompositeRequest request, DownlinkRequestCallback<ObserveCompositeRequest, public void sendObserveCompositeRequest(LwM2mClient client, TbLwM2MObserveCompositeRequest request, DownlinkRequestCallback<ObserveCompositeRequest,
ObserveCompositeResponse> callback) { ObserveCompositeResponse> callback) {
try { try {
log.trace("[{}] Send Composite observation: [{}].", client.getEndpoint(), request.getObjectIds());
ContentFormat compositeContentFormat = this.findFirstContentFormatForComposite(client.getClientSupportContentFormats()); ContentFormat compositeContentFormat = this.findFirstContentFormatForComposite(client.getClientSupportContentFormats());
ObserveCompositeRequest downlink = new ObserveCompositeRequest(compositeContentFormat, compositeContentFormat, request.getObjectIds()); String resourceExisting = checkResourceForExistingComposite(client, request.getObjectIds());
sendCompositeRequest(client, downlink, this.config.getTimeout(), callback); if (StringUtils.isNotBlank(resourceExisting)) {
callback.onValidationError(request.toString(), resourceExisting);
} else {
ObserveCompositeRequest downlink = new ObserveCompositeRequest(compositeContentFormat, compositeContentFormat, request.getObjectIds());
log.trace("[{}] Send ObserveComposite: {}.", client.getEndpoint(), request.getVersionedIds());
sendCompositeRequest(client, downlink, this.config.getTimeout(), callback);
}
} catch (InvalidRequestException e) { } catch (InvalidRequestException e) {
callback.onValidationError(request.toString(), e.getMessage()); callback.onValidationError(request.toString(), e.getMessage());
} }
@ -246,44 +243,18 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
@Override @Override
public void sendCancelObserveCompositeRequest(LwM2mClient client, TbLwM2MCancelObserveCompositeRequest request, DownlinkRequestCallback<TbLwM2MCancelObserveCompositeRequest, Integer> callback) { public void sendCancelObserveCompositeRequest(LwM2mClient client, TbLwM2MCancelObserveCompositeRequest request, DownlinkRequestCallback<TbLwM2MCancelObserveCompositeRequest, Integer> callback) {
try { try {
Set<Observation> observations = context.getServer().getObservationService().getObservations(client.getRegistration()); log.trace("[{}] Send CancelObserveComposite: {}.", client.getEndpoint(), request.getVersionedIds());
List<LwM2mPath> listPath = LwM2mPath.getLwM2mPathList(Arrays.asList(request.getObjectIds())); int cnt = context.getServer().getObservationService().cancelCompositeObservations(client.getRegistration(), request.getObjectIds());
Optional<Observation> observationOpt = Optional.ofNullable(observations.stream().filter(observation -> observation instanceof CompositeObservation && ((CompositeObservation) observation).getPaths().equals(listPath)).findFirst().orElse(null)); if (cnt != 0) {
int cnt = 0;
if (observationOpt.isPresent()) {
cnt = context.getServer().getObservationService().cancelCompositeObservations(client.getRegistration(), request.getObjectIds());
callback.onSuccess(request, cnt); callback.onSuccess(request, cnt);
} else { } else {
Set<String> lwPaths = new HashSet<>(); callback.onValidationError(request.toString(), "Could not find active Observe Composite component with paths: " + Arrays.toString(request.getVersionedIds()));
for (Observation obs : observations) {
LwM2mPath lwPathObs = ((SingleObservation) obs).getPath();
for (LwM2mPath nodePath : listPath) {
String validNodePath = validatePathObserveCancelAny(nodePath, lwPathObs, client);
if (validNodePath != null) lwPaths.add(validNodePath);
}
};
for (String nodePath : lwPaths) {
cnt += context.getServer().getObservationService().cancelObservations(client.getRegistration(), nodePath);
}
} }
callback.onSuccess(request, cnt); } catch (InvalidRequestException e) {
} catch (ThingsboardException e){
callback.onValidationError(request.toString(), e.getMessage()); callback.onValidationError(request.toString(), e.getMessage());
} }
} }
private String validatePathObserveCancelAny(LwM2mPath nodePath, LwM2mPath lwPathObs, LwM2mClient client) throws ThingsboardException {
if (nodePath.equals(lwPathObs) || lwPathObs.startWith(nodePath)) { // nodePath = "3", lwPathObs = "3/0/9": cancel for tne all lwPathObs
return lwPathObs.toString();
} else if (!nodePath.equals(lwPathObs) && nodePath.startWith(lwPathObs)) {
String errorMsg = String.format(
"Unexpected error: There is registration with Endpoint %s for observation path [%s], that includes this observation path [%s]",
client.getRegistration().getEndpoint(), lwPathObs, nodePath);
throw new ThingsboardException(errorMsg, ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
return null;
}
@Override @Override
public void sendDiscoverAllRequest(LwM2mClient client, TbLwM2MDiscoverAllRequest request, DownlinkRequestCallback<TbLwM2MDiscoverAllRequest, List<String>> callback) { public void sendDiscoverAllRequest(LwM2mClient client, TbLwM2MDiscoverAllRequest request, DownlinkRequestCallback<TbLwM2MDiscoverAllRequest, List<String>> callback) {
callback.onSuccess(request, Arrays.stream(client.getRegistration().getSortedObjectLinks()).map(Link::toCoreLinkFormat).collect(Collectors.toList())); callback.onSuccess(request, Arrays.stream(client.getRegistration().getSortedObjectLinks()).map(Link::toCoreLinkFormat).collect(Collectors.toList()));
@ -334,22 +305,15 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
@Override @Override
public void sendCancelObserveRequest(LwM2mClient client, TbLwM2MCancelObserveRequest request, DownlinkRequestCallback<TbLwM2MCancelObserveRequest, Integer> callback) { public void sendCancelObserveRequest(LwM2mClient client, TbLwM2MCancelObserveRequest request, DownlinkRequestCallback<TbLwM2MCancelObserveRequest, Integer> callback) {
try{ try {
validateVersionedId(client, request); log.trace("[{}] Send CancelObserve {}.", client.getEndpoint(), request.getVersionedId());
Set<Observation> observations = context.getServer().getObservationService().getObservations(client.getRegistration()); int cnt = context.getServer().getObservationService().cancelObservations(client.getRegistration(), request.getObjectId());
int observeCancelCnt = 0; if (cnt != 0) {
Set<String> lwPaths = new HashSet<>(); callback.onSuccess(request, cnt);
for (Observation obs : observations) { } else {
LwM2mPath lwPathObs = ((SingleObservation) obs).getPath(); callback.onValidationError(request.toString(), "Could not find active Observe component with path: " + request.getVersionedId());
LwM2mPath nodePath = new LwM2mPath(request.getObjectId());
String validNodePath = validatePathObserveCancelAny(nodePath, lwPathObs, client);
if (validNodePath != null) lwPaths.add(validNodePath);
};
for (String nodePath : lwPaths) {
observeCancelCnt += context.getServer().getObservationService().cancelObservations(client.getRegistration(), nodePath);
} }
callback.onSuccess(request, observeCancelCnt); } catch (InvalidRequestException e) {
} catch (ThingsboardException e){
callback.onValidationError(request.toString(), e.getMessage()); callback.onValidationError(request.toString(), e.getMessage());
} }
} }
@ -416,7 +380,7 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
addAttribute(attributes, OBJECT_VERSION, params.getVer()); // Attachment.OBJECT addAttribute(attributes, OBJECT_VERSION, params.getVer()); // Attachment.OBJECT
} }
return new LwM2mAttributeSet(attributes); return new LwM2mAttributeSet(attributes);
} }
@Override @Override
@ -702,13 +666,13 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
addAttribute(attributes, attribute, value, null, null); addAttribute(attributes, attribute, value, null, null);
} }
private static <T> void addAttribute(List<LwM2mAttribute<?>> attributes, LwM2mAttributeModel<T> attribute, T value, Function<T, ?> converter) { private static <T> void addAttribute(List<LwM2mAttribute<?>> attributes, LwM2mAttributeModel<T> attribute, T value, Function<T, ?> converter) {
addAttribute(attributes, attribute, value, null, converter); addAttribute(attributes, attribute, value, null, converter);
} }
private static <T> void addAttribute(List<LwM2mAttribute<?>> attributes, LwM2mAttributeModel<T> attributeName, T value, Predicate<T> filter, Function<T, ?> converter) { private static <T> void addAttribute(List<LwM2mAttribute<?>> attributes, LwM2mAttributeModel<T> attributeName, T value, Predicate<T> filter, Function<T, ?> converter) {
if (value != null && ((filter == null) || filter.test(value))) { if (value != null && ((filter == null) || filter.test(value))) {
T valueConvert = (T) converter != null ? (T) converter.apply(value) : value; T valueConvert = (T) converter != null ? (T) converter.apply(value) : value;
attributes.add(new LwM2mAttribute<>(attributeName, valueConvert)); attributes.add(new LwM2mAttribute<>(attributeName, valueConvert));
} }
} }
@ -746,12 +710,12 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
if (resourceModel != null && (pathIds.isResourceInstance() || (pathIds.isResource() && !resourceModel.multiple))) { if (resourceModel != null && (pathIds.isResourceInstance() || (pathIds.isResource() && !resourceModel.multiple))) {
ContentFormat[] desiredFormats; ContentFormat[] desiredFormats;
if (OBJLNK.equals(resourceModel.type)) { if (OBJLNK.equals(resourceModel.type)) {
desiredFormats = new ContentFormat[]{ContentFormat.LINK, ContentFormat.CBOR, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON}; desiredFormats = new ContentFormat[]{ContentFormat.LINK, ContentFormat.CBOR, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON};
} else if (OPAQUE.equals(resourceModel.type)) { } else if (OPAQUE.equals(resourceModel.type)) {
desiredFormats = new ContentFormat[]{ContentFormat.OPAQUE, ContentFormat.CBOR, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON}; desiredFormats = new ContentFormat[]{ContentFormat.OPAQUE, ContentFormat.CBOR, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON};
} else { } else {
desiredFormats = new ContentFormat[]{ContentFormat.CBOR, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON}; desiredFormats = new ContentFormat[]{ContentFormat.CBOR, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON};
} }
return findFirstContentFormatForComp(client.getClientSupportContentFormats(), client.getDefaultContentFormat(), desiredFormats); return findFirstContentFormatForComp(client.getClientSupportContentFormats(), client.getDefaultContentFormat(), desiredFormats);
} else { } else {
return getContentFormatForComplex(client); return getContentFormatForComplex(client);
@ -775,6 +739,7 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
throw new RuntimeException("The version " + client.getRegistration().getLwM2mVersion() + " is not supported!"); throw new RuntimeException("The version " + client.getRegistration().getLwM2mVersion() + " is not supported!");
} }
} }
private <R> String toString(R request) { private <R> String toString(R request) {
try { try {
return request != null ? request.toString() : ""; return request != null ? request.toString() : "";
@ -784,7 +749,7 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
} }
} }
private ContentFormat findFirstContentFormatForComposite (Set<ContentFormat> clientSupportContentFormats) { private ContentFormat findFirstContentFormatForComposite(Set<ContentFormat> clientSupportContentFormats) {
ContentFormat contentFormat = findFirstContentFormatForComp(clientSupportContentFormats, null, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON); ContentFormat contentFormat = findFirstContentFormatForComp(clientSupportContentFormats, null, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON);
if (contentFormat != null) { if (contentFormat != null) {
return contentFormat; return contentFormat;
@ -792,8 +757,9 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
throw new RuntimeException("This device does not support Composite Operation"); throw new RuntimeException("This device does not support Composite Operation");
} }
} }
private static ContentFormat findFirstContentFormatForComp(Set<ContentFormat> clientSupportContentFormats, ContentFormat defaultValue, ContentFormat... desiredFormats) { private static ContentFormat findFirstContentFormatForComp(Set<ContentFormat> clientSupportContentFormats, ContentFormat defaultValue, ContentFormat... desiredFormats) {
List desiredFormatsList = Arrays.asList(desiredFormats); List desiredFormatsList = Arrays.asList(desiredFormats);
for (ContentFormat c : clientSupportContentFormats) { for (ContentFormat c : clientSupportContentFormats) {
if (desiredFormatsList.contains(c)) { if (desiredFormatsList.contains(c)) {
return c; return c;
@ -801,4 +767,61 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
} }
return defaultValue; return defaultValue;
} }
/**
* Check if at least one of the resource objectIds (Composite) in SingleObservation or CompositeObservation is already registered
* @param objectIds
* @return
*/
private String checkResourceForExistingComposite(LwM2mClient client, String[] objectIds) {
List<String> objectIdsList = Arrays.asList(objectIds);
Set<Observation> observations = context.getServer().getObservationService().getObservations(client.getRegistration());
for (Observation observation : observations) {
if (observation instanceof SingleObservation singleObs) {
String idSingleOb = singleObs.getPath().toString();
if (objectIdsList.contains(idSingleOb)) {
return "Resource [" + idSingleOb + "] is already registered as SingleObservation.";
}
} else if (observation instanceof CompositeObservation compObs) {
String paths = compObs.getPaths().toString();
for (String idCompOb : objectIds) {
if (paths.contains(idCompOb)) {
return "Resource [" + idCompOb + "] is already registered in CompositeObservation.";
}
}
}
}
return null;
}
/**
* Check if the resource SingleObservation is already registered in CompositeObservation
* Check if the resource SingleObservation is already registered in SingleObservation and (not equals path
* @param objectId
* @return
*/
private String checkResourceSingleObservationForExisting(LwM2mClient client, String objectId) {
Set<Observation> observations = context.getServer().getObservationService().getObservations(client.getRegistration());
for (Observation observation : observations) {
if (observation instanceof SingleObservation singleObs) {
LwM2mPath pathSingleOb = singleObs.getPath();
LwM2mPath pathObjectId = new LwM2mPath(objectId);
if (!pathSingleOb.toString().equals(objectId)) {
List paths = Arrays.asList(pathSingleOb, pathObjectId);
try {
LwM2mPath.validateNotOverlapping(paths);
} catch (IllegalArgumentException e){
return "Resource [" + objectId + "] conflict with is already registered as SingleObservation [" + pathSingleOb + "].";
}
}
}
else if (observation instanceof CompositeObservation compObs) {
String paths = compObs.getPaths().toString();
if (paths.contains(objectId)) {
return "Resource [" + objectId + "] is already registered in CompositeObservation.";
}
}
}
return null;
}
} }

166
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbInMemoryRegistrationStore.java

@ -15,25 +15,16 @@
*/ */
package org.thingsboard.server.transport.lwm2m.server.store; package org.thingsboard.server.transport.lwm2m.server.store;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.coap.Token;
import org.eclipse.californium.core.network.RandomTokenGenerator;
import org.eclipse.californium.core.network.TokenGenerator; import org.eclipse.californium.core.network.TokenGenerator;
import org.eclipse.californium.core.network.TokenGenerator.Scope;
import org.eclipse.leshan.core.Destroyable; import org.eclipse.leshan.core.Destroyable;
import org.eclipse.leshan.core.Startable; import org.eclipse.leshan.core.Startable;
import org.eclipse.leshan.core.Stoppable; import org.eclipse.leshan.core.Stoppable;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.observation.CompositeObservation; import org.eclipse.leshan.core.observation.CompositeObservation;
import org.eclipse.leshan.core.observation.Observation; import org.eclipse.leshan.core.observation.Observation;
import org.eclipse.leshan.core.observation.ObservationIdentifier; import org.eclipse.leshan.core.observation.ObservationIdentifier;
import org.eclipse.leshan.core.observation.SingleObservation; import org.eclipse.leshan.core.observation.SingleObservation;
import org.eclipse.leshan.core.peer.LwM2mIdentity; import org.eclipse.leshan.core.peer.LwM2mIdentity;
import org.eclipse.leshan.core.request.ContentFormat;
import org.eclipse.leshan.core.util.NamedThreadFactory; import org.eclipse.leshan.core.util.NamedThreadFactory;
import org.eclipse.leshan.server.registration.Deregistration; import org.eclipse.leshan.server.registration.Deregistration;
import org.eclipse.leshan.server.registration.ExpirationListener; import org.eclipse.leshan.server.registration.ExpirationListener;
@ -41,14 +32,12 @@ import org.eclipse.leshan.server.registration.Registration;
import org.eclipse.leshan.server.registration.RegistrationStore; import org.eclipse.leshan.server.registration.RegistrationStore;
import org.eclipse.leshan.server.registration.RegistrationUpdate; import org.eclipse.leshan.server.registration.RegistrationUpdate;
import org.eclipse.leshan.server.registration.UpdatedRegistration; import org.eclipse.leshan.server.registration.UpdatedRegistration;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.server.LwM2mVersionedModelProvider; import org.thingsboard.server.transport.lwm2m.server.LwM2mVersionedModelProvider;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
@ -60,13 +49,9 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
import static org.eclipse.leshan.core.californium.ObserveUtil.CTX_CF_OBERSATION;
import static org.eclipse.leshan.core.californium.ObserveUtil.extractSerializedObservation;
@Slf4j @Slf4j
public class TbInMemoryRegistrationStore implements RegistrationStore, Startable, Stoppable, Destroyable { public class TbInMemoryRegistrationStore implements RegistrationStore, Startable, Stoppable, Destroyable {
@ -108,7 +93,7 @@ public class TbInMemoryRegistrationStore implements RegistrationStore, Startable
this.schedExecutor = schedExecutor; this.schedExecutor = schedExecutor;
this.cleanPeriod = cleanPeriodInSec; this.cleanPeriod = cleanPeriodInSec;
this.modelProvider = modelProvider; this.modelProvider = modelProvider;
this.config = config; this.config = config;
} }
/* *************** Leshan Registration API **************** */ /* *************** Leshan Registration API **************** */
@ -255,138 +240,60 @@ public class TbInMemoryRegistrationStore implements RegistrationStore, Startable
List<Observation> removed = new ArrayList<>(); List<Observation> removed = new ArrayList<>();
try { try {
lock.writeLock().lock(); lock.writeLock().lock();
if (!regsByRegId.containsKey(registrationId)) { if (!regsByRegId.containsKey(registrationId)) {
throw new IllegalStateException(String.format( throw new IllegalStateException(String.format(
"can not add observation %s there is no registration with id %s", observation, registrationId)); "can not add observation %s there is no registration with id %s", observation, registrationId));
} }
updateObservation(registrationId, observation, addIfAbsent, removed);
if (observation instanceof SingleObservation) {
if (validateObserveResource(((SingleObservation)observation).getPath(), registrationId)) {
updateSingleObservation(registrationId, (SingleObservation) observation, addIfAbsent, removed);
// cancel existing observations for the same path and registration id.
cancelObservation (observation, registrationId, removed);
}
} else {
ContentFormat ct = ((CompositeObservation) observation).getResponseContentFormat();
Map<String, String> ctx = observation.getContext();
String serializedObservation = extractSerializedObservation(observation);
JsonNode nodeSerObs = JacksonUtil.toJsonNode(serializedObservation);
((CompositeObservation)observation).getPaths().forEach(path -> {
if (validateObserveResource(path, registrationId)) {
String serializedObs = createSerializedSingleObservation(nodeSerObs, path.toString());
Observation singleObservation = createSingleObservation(registrationId, path, ct, ctx, serializedObs, getTokenGenerator());
updateSingleObservation(registrationId, (SingleObservation) singleObservation, addIfAbsent, removed);
// cancel existing observations for the same path and registration id.
cancelObservation (singleObservation, registrationId, removed);
}
});
}
} finally { } finally {
lock.writeLock().unlock(); lock.writeLock().unlock();
} }
return removed; return removed;
} }
private boolean validateObserveResource(LwM2mPath path, String registrationId){ private void updateObservation(String registrationId, Observation observation, boolean addIfAbsent, List<Observation> removed) {
// check if the resource is readable.
if (path.isResource() || path.isResourceInstance()) {
ObjectModel objectModel = modelProvider.getObjectModel(getRegistration(registrationId)).getObjectModel(path.getObjectId());
ResourceModel resourceModel = objectModel == null ? null : objectModel.resources.get(path.getResourceId());
if (resourceModel == null) {
return false;
} else if (!resourceModel.operations.isReadable()) {
return false;
} else if (path.isResourceInstance() && !resourceModel.multiple) {
return false;
}
}
return true;
}
private void updateSingleObservation (String registrationId, SingleObservation observation, boolean addIfAbsent, List<Observation> removed) {
// Absorption by existing Observations
// Absorption by existing Observations
Observation previousObservation = null; Observation previousObservation = null;
SingleObservation existingObservation = null;
ObservationIdentifier id = observation.getId(); ObservationIdentifier id = observation.getId();
if (addIfAbsent) { if (addIfAbsent) {
if (!obsByToken.containsKey(id)) { if (!obsByToken.containsKey(id)) {
existingObservation = validateByAbsorptionExistingObservations(observation); previousObservation = obsByToken.put(id, observation);
if (existingObservation == null) { } else {
obsByToken.put(id, observation); obsByToken.put(id, observation);
} else if (!existingObservation.getPath().equals(observation.getPath())){
obsByToken.put(id, observation);
previousObservation = obsByToken.get(existingObservation.getId());
}
} }
} else { } else {
previousObservation = obsByToken.put(id, observation); previousObservation = obsByToken.put(id, observation);
} }
if (!tokensByRegId.containsKey(registrationId)) { if (!tokensByRegId.containsKey(registrationId)) {
tokensByRegId.put(registrationId, new HashSet<ObservationIdentifier>()); tokensByRegId.put(registrationId, new HashSet<ObservationIdentifier>());
} }
tokensByRegId.get(registrationId).add(id);
if (existingObservation == null || !existingObservation.getPath().equals(observation.getPath())) {
tokensByRegId.get(registrationId).add(id);
}
// log any collisions // log any collisions
if (addIfAbsent && previousObservation != null) { if (previousObservation != null) {
if (!existingObservation.getPath().equals(observation.getPath())) { removed.add(previousObservation);
removed.add(previousObservation); log.warn("Token collision ? observation [{}] will be replaced by observation [{}] ",
log.warn("Token collision ? observation [{}] will be replaced by observation [{}], that this observation includes input observation [{}]!", previousObservation, observation);
previousObservation, observation, observation);
} else {
log.warn("Token collision ? existing observation [{}] includes input observation [{}]",
existingObservation, observation);
}
} }
} // cancel existing observations for the same path and registration id.
for (Observation obs : unsafeGetObservations(registrationId)) {
private SingleObservation validateByAbsorptionExistingObservations (SingleObservation observation) { if (areTheSamePaths(observation, obs) && !observation.getId().equals(obs.getId())) {
LwM2mPath pathObservation = observation.getPath(); unsafeRemoveObservation(obs.getId());
AtomicReference<SingleObservation> result = new AtomicReference<>(); removed.add(obs);
obsByToken.values().stream().forEach(obs -> {
LwM2mPath pathObs = ((SingleObservation)obs).getPath();
if ((!pathObservation.equals(pathObs) && pathObs.startWith(pathObservation)) || // pathObs = "3/0/9"-> pathObservation = "3"
(pathObservation.equals(pathObs) && !observation.getId().equals(obs.getId()))) {
result.set((SingleObservation)obs);
} else if (!pathObservation.equals(pathObs) && pathObservation.startWith(pathObs)) { // pathObs = "3" -> pathObservation = "3/0/9"
result.set(observation);
} }
});
return result.get();
}
private TokenGenerator getTokenGenerator(){
if (this.tokenGenerator == null) {
this.tokenGenerator = new RandomTokenGenerator(config.getCoapConfig());
} }
return this.tokenGenerator;
} }
public static SingleObservation createSingleObservation(String registrationId, LwM2mPath target, ContentFormat ct, private boolean areTheSamePaths(Observation observation, Observation obs) {
Map<String, String> ctx, String serializedObservation, TokenGenerator tokenGenerator) { if (observation instanceof SingleObservation && obs instanceof SingleObservation) {
Token token = tokenGenerator.createToken(Scope.SHORT_TERM); return ((SingleObservation) observation).getPath().equals(((SingleObservation) obs).getPath());
Map<String, String> protocolData = Collections.emptyMap();
if (serializedObservation != null) {
protocolData = new HashMap<>();
protocolData.put(CTX_CF_OBERSATION, serializedObservation);
} }
return new SingleObservation(new ObservationIdentifier(token.getBytes()), registrationId, target, ct, ctx, protocolData); if (observation instanceof CompositeObservation && obs instanceof CompositeObservation) {
} return ((CompositeObservation) observation).getPaths().equals(((CompositeObservation) obs).getPaths());
public static String createSerializedSingleObservation(JsonNode nodeSerObs, String path){
if (nodeSerObs.has("context")){
((ObjectNode) nodeSerObs.get("context")).put("leshan-path", path + "\n");
return JacksonUtil.toString(nodeSerObs);
} }
return null; return false;
} }
@Override @Override
@ -394,6 +301,12 @@ public class TbInMemoryRegistrationStore implements RegistrationStore, Startable
try { try {
lock.writeLock().lock(); lock.writeLock().lock();
Observation observation = unsafeGetObservation(observationId); Observation observation = unsafeGetObservation(observationId);
if (observation instanceof SingleObservation){
log.trace("(SingleObservation) removeObservation: [{}]", ((SingleObservation)observation).getPath());
} else {
log.trace("(CompositeObservation) removeObservation: [{}]", ((CompositeObservation)observation).getPaths());
}
if (observation != null && registrationId.equals(observation.getRegistrationId())) { if (observation != null && registrationId.equals(observation.getRegistrationId())) {
unsafeRemoveObservation(observationId); unsafeRemoveObservation(observationId);
return observation; return observation;
@ -470,24 +383,6 @@ public class TbInMemoryRegistrationStore implements RegistrationStore, Startable
return obs; return obs;
} }
private void cancelObservation (Observation observation, String registrationId, List<Observation> removed) {
for (Observation obs : unsafeGetObservations(registrationId)) {
cancelExistingObservation(observation, obs, removed);
}
}
private void cancelExistingObservation(Observation observation, Observation obs, List<Observation> removed) {
LwM2mPath pathObservation = ((SingleObservation)observation).getPath();
LwM2mPath pathObs = ((SingleObservation)obs).getPath();
if ((!pathObservation.equals(pathObs) && pathObs.startWith(pathObservation)) || // pathObservation = "3", pathObs = "3/0/9"
(pathObservation.equals(pathObs) && !observation.getId().equals(obs.getId()))) {
unsafeRemoveObservation(obs.getId());
removed.add(obs);
} else if (!pathObservation.equals(pathObs) && pathObservation.startWith(pathObs)) { // pathObservation = "3/0/9", pathObs = "3"
unsafeRemoveObservation(observation.getId());
}
}
private void unsafeRemoveObservation(ObservationIdentifier observationId) { private void unsafeRemoveObservation(ObservationIdentifier observationId) {
Observation removed = obsByToken.remove(observationId); Observation removed = obsByToken.remove(observationId);
if (removed != null) { if (removed != null) {
@ -500,7 +395,6 @@ public class TbInMemoryRegistrationStore implements RegistrationStore, Startable
} }
} }
/** /**
* CancelAllObservation * CancelAllObservation
* @param registrationId * @param registrationId

135
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java

@ -15,7 +15,6 @@
*/ */
package org.thingsboard.server.transport.lwm2m.server.store; package org.thingsboard.server.transport.lwm2m.server.store;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.coap.Token; import org.eclipse.californium.core.coap.Token;
import org.eclipse.californium.core.network.RandomTokenGenerator; import org.eclipse.californium.core.network.RandomTokenGenerator;
@ -33,10 +32,8 @@ import org.eclipse.leshan.core.observation.Observation;
import org.eclipse.leshan.core.observation.ObservationIdentifier; import org.eclipse.leshan.core.observation.ObservationIdentifier;
import org.eclipse.leshan.core.observation.SingleObservation; import org.eclipse.leshan.core.observation.SingleObservation;
import org.eclipse.leshan.core.peer.LwM2mIdentity; import org.eclipse.leshan.core.peer.LwM2mIdentity;
import org.eclipse.leshan.core.request.ContentFormat;
import org.eclipse.leshan.core.util.NamedThreadFactory; import org.eclipse.leshan.core.util.NamedThreadFactory;
import org.eclipse.leshan.core.util.Validate; import org.eclipse.leshan.core.util.Validate;
import org.eclipse.leshan.server.redis.RedisRegistrationStore;
import org.eclipse.leshan.server.redis.serialization.ObservationSerDes; import org.eclipse.leshan.server.redis.serialization.ObservationSerDes;
import org.eclipse.leshan.server.redis.serialization.RegistrationSerDes; import org.eclipse.leshan.server.redis.serialization.RegistrationSerDes;
import org.eclipse.leshan.server.registration.Deregistration; import org.eclipse.leshan.server.registration.Deregistration;
@ -45,15 +42,12 @@ import org.eclipse.leshan.server.registration.Registration;
import org.eclipse.leshan.server.registration.RegistrationStore; import org.eclipse.leshan.server.registration.RegistrationStore;
import org.eclipse.leshan.server.registration.RegistrationUpdate; import org.eclipse.leshan.server.registration.RegistrationUpdate;
import org.eclipse.leshan.server.registration.UpdatedRegistration; import org.eclipse.leshan.server.registration.UpdatedRegistration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.RedisClusterConnection; import org.springframework.data.redis.connection.RedisClusterConnection;
import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions; import org.springframework.data.redis.core.ScanOptions;
import org.springframework.integration.redis.util.RedisLockRegistry; import org.springframework.integration.redis.util.RedisLockRegistry;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.server.LwM2mVersionedModelProvider; import org.thingsboard.server.transport.lwm2m.server.LwM2mVersionedModelProvider;
@ -65,19 +59,14 @@ import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.leshan.core.californium.ObserveUtil.extractSerializedObservation;
import static org.thingsboard.server.transport.lwm2m.server.store.TbInMemoryRegistrationStore.createSerializedSingleObservation;
import static org.thingsboard.server.transport.lwm2m.server.store.TbInMemoryRegistrationStore.createSingleObservation;
@Slf4j @Slf4j
public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startable, Stoppable, Destroyable { public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startable, Stoppable, Destroyable {
@ -87,8 +76,6 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab
/** Defaut Extra time for registration lifetime in seconds */ /** Defaut Extra time for registration lifetime in seconds */
public static final long DEFAULT_GRACE_PERIOD = 0; public static final long DEFAULT_GRACE_PERIOD = 0;
private static final Logger LOG = LoggerFactory.getLogger(RedisRegistrationStore.class);
// Redis key prefixes // Redis key prefixes
public static final String REG_EP = "REG:EP:"; // (Endpoint => Registration) public static final String REG_EP = "REG:EP:"; // (Endpoint => Registration)
private static final String REG_EP_REGID_IDX = "EP:REGID:"; // secondary index key (Registration ID => Endpoint) private static final String REG_EP_REGID_IDX = "EP:REGID:"; // secondary index key (Registration ID => Endpoint)
@ -131,7 +118,7 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab
public TbLwM2mRedisRegistrationStore(LwM2MTransportServerConfig config, RedisConnectionFactory connectionFactory, long cleanPeriodInSec, long lifetimeGracePeriodInSec, int cleanLimit, LwM2mVersionedModelProvider modelProvider) { public TbLwM2mRedisRegistrationStore(LwM2MTransportServerConfig config, RedisConnectionFactory connectionFactory, long cleanPeriodInSec, long lifetimeGracePeriodInSec, int cleanLimit, LwM2mVersionedModelProvider modelProvider) {
this(config, connectionFactory, Executors.newScheduledThreadPool(1, this(config, connectionFactory, Executors.newScheduledThreadPool(1,
new NamedThreadFactory(String.format("RedisRegistrationStore Cleaner (%ds)", cleanPeriodInSec))), new NamedThreadFactory(String.format("RedisRegistrationStore Cleaner (%ds)", cleanPeriodInSec))),
cleanPeriodInSec, lifetimeGracePeriodInSec, cleanLimit, modelProvider); cleanPeriodInSec, lifetimeGracePeriodInSec, cleanLimit, modelProvider);
} }
@ -281,6 +268,7 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab
return getRegistration(connection, registrationId); return getRegistration(connection, registrationId);
} }
} }
private Registration getRegistration(RedisConnection connection, String registrationId) { private Registration getRegistration(RedisConnection connection, String registrationId) {
byte[] ep = connection.get(toRegIdKey(registrationId)); byte[] ep = connection.get(toRegIdKey(registrationId));
if (ep == null) { if (ep == null) {
@ -494,31 +482,10 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab
Lock lock = null; Lock lock = null;
String lockKey = toLockKey(ep); String lockKey = toLockKey(ep);
try { try {
lock = redisLock.obtain(lockKey); lock = redisLock.obtain(lockKey);
lock.lock(); lock.lock();
if (observation instanceof SingleObservation) { updateObservation(registrationId, observation, addIfAbsent, removed, connection);
if (validateObserveResource(((SingleObservation)observation).getPath(), registrationId)) {
updateSingleObservation(registrationId, (SingleObservation)observation, addIfAbsent, removed, connection);
// cancel existing observations for the same path and registration id.
cancelObservation(observation, registrationId, removed, connection);
}
} else {
ContentFormat ct = ((CompositeObservation) observation).getResponseContentFormat();
Map<String, String> ctx = observation.getContext();
String serializedObservation = extractSerializedObservation(observation);
JsonNode nodeSerObs = JacksonUtil.toJsonNode(serializedObservation);
((CompositeObservation)observation).getPaths().forEach(path -> {
if (validateObserveResource(path, registrationId)) {
String serializedObs = createSerializedSingleObservation(nodeSerObs, path.toString());
SingleObservation singleObservation = createSingleObservation(registrationId, path, ct, ctx, serializedObs, getTokenGenerator());
updateSingleObservation(registrationId, singleObservation, addIfAbsent, removed, connection);
// cancel existing observations for the same path and registration id.
cancelObservation (singleObservation, registrationId, removed, connection);
}
});
}
} finally { } finally {
if (lock != null) { if (lock != null) {
lock.unlock(); lock.unlock();
@ -527,43 +494,21 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab
} }
return removed; return removed;
} }
private void updateObservation(String registrationId, Observation observation, boolean addIfAbsent,
private boolean validateObserveResource(LwM2mPath path, String registrationId){ List<Observation> removed, RedisConnection connection) {
// check if the resource is readable.
if (path.isResource() || path.isResourceInstance()) {
ObjectModel objectModel = modelProvider.getObjectModel(getRegistration(registrationId)).getObjectModel(path.getObjectId());
ResourceModel resourceModel = objectModel == null ? null : objectModel.resources.get(path.getResourceId());
if (resourceModel == null) {
return false;
} else if (!resourceModel.operations.isReadable()) {
return false;
} else if (path.isResourceInstance() && !resourceModel.multiple) {
return false;
}
}
return true;
}
private void updateSingleObservation (String registrationId, SingleObservation observation, boolean addIfAbsent,
List<Observation> removed, RedisConnection connection) {
// Add and Get previous observation // Add and Get previous observation
byte[] previousValue; byte[] previousValue;
byte[] key = toKey(OBS_TKN, observation.getId().getBytes()); byte[] key = toKey(OBS_TKN, observation.getId().getBytes());
byte[] serializeObs = serializeObs(observation); byte[] serializeObs = serializeObs(observation);
// we analyze the present previous value // we analyze the present previous value
SingleObservation existingObservation = null; if (addIfAbsent) {
if (addIfAbsent){
previousValue = connection.stringCommands().get(key); previousValue = connection.stringCommands().get(key);
if (previousValue == null) { if (previousValue == null) {
existingObservation = validateByAbsorptionExistingObservations(observation, connection); connection.stringCommands().set(key, serializeObs);
if (existingObservation == null){ previousValue = serializeObs;
connection.stringCommands().set(key, serializeObs); } else {
} else if(!existingObservation.getPath().equals(observation.getPath())) { connection.stringCommands().set(key, serializeObs);
connection.stringCommands().set(key, serializeObs);
previousValue = serializeObs(existingObservation);
}
} }
} else { } else {
previousValue = connection.stringCommands().getSet(key, serializeObs); previousValue = connection.stringCommands().getSet(key, serializeObs);
@ -573,25 +518,38 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab
connection.listCommands().lPush(toKey(OBS_TKNS_REGID_IDX, registrationId), observation.getId().getBytes()); connection.listCommands().lPush(toKey(OBS_TKNS_REGID_IDX, registrationId), observation.getId().getBytes());
// log any collisions // log any collisions
if (addIfAbsent && previousValue != null) { Observation previousObservation;
if (!existingObservation.getPath().equals(observation.getPath())) { if (previousValue != null && previousValue.length != 0) {
Observation previousObservation = deserializeObs(previousValue); previousObservation = deserializeObs(previousValue);
removed.add(previousObservation); log.warn("Token collision ? observation [{}] will be replaced by observation [{}] ",
LOG.warn("Token collision ? observation [{}] will be replaced by observation [{}], that this observation includes input observation [{}]!", previousObservation, observation);
previousObservation, observation, observation); }
} else {
LOG.warn("Token collision ? existing observation [{}] includes input observation [{}]", // cancel existing observations for the same path and registration id.
existingObservation, observation); for (Observation obs : getObservations(connection, registrationId)) {
if (areTheSamePaths(observation, obs) && !observation.getId().equals(obs.getId())) {
removed.add(obs);
unsafeRemoveObservation(connection, registrationId, obs.getId().getBytes());
} }
} }
} }
private boolean areTheSamePaths(Observation observation, Observation obs) {
if (observation instanceof SingleObservation && obs instanceof SingleObservation) {
return ((SingleObservation) observation).getPath().equals(((SingleObservation) obs).getPath());
}
if (observation instanceof CompositeObservation && obs instanceof CompositeObservation) {
return ((CompositeObservation) observation).getPaths().equals(((CompositeObservation) obs).getPaths());
}
return false;
}
@Override @Override
public Collection<Observation> getObservations(String registrationId) { public Collection<Observation> getObservations(String registrationId) {
try (var connection = connectionFactory.getConnection()) { try (var connection = connectionFactory.getConnection()) {
return getObservations(connection, registrationId); return getObservations(connection, registrationId);
} }
} }
@Override @Override
public Observation getObservation(String registrationId, ObservationIdentifier observationId) { public Observation getObservation(String registrationId, ObservationIdentifier observationId) {
return getObservations(registrationId).stream().filter( return getObservations(registrationId).stream().filter(
@ -654,25 +612,6 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab
return result; return result;
} }
private SingleObservation validateByAbsorptionExistingObservations(SingleObservation observation, RedisConnection connection) {
LwM2mPath pathObservation = observation.getPath();
AtomicReference<SingleObservation> result = new AtomicReference<>();
Collection<Observation> observations = getObservations(connection, observation.getRegistrationId());
observations.stream().forEach(obs -> {
LwM2mPath pathObs = ((SingleObservation)obs).getPath();
if ((!pathObservation.equals(pathObs) && pathObs.startWith(pathObservation)) || // pathObs = "3/0/9"-> pathObservation = "3"
(pathObservation.equals(pathObs) && !observation.getId().equals(obs.getId()))) {
result.set((SingleObservation)obs);
} else if (!pathObservation.equals(pathObs) && pathObservation.startWith(pathObs)) { // pathObs = "3" -> pathObservation = "3/0/9"
result.set(observation);
}
});
return result.get();
}
@Override @Override
public Collection<Observation> removeObservations(String registrationId) { public Collection<Observation> removeObservations(String registrationId) {
try (var connection = connectionFactory.getConnection()) { try (var connection = connectionFactory.getConnection()) {
@ -710,7 +649,7 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab
/* *************** Observation utility functions **************** */ /* *************** Observation utility functions **************** */
private TokenGenerator getTokenGenerator(){ private TokenGenerator getTokenGenerator() {
if (this.tokenGenerator == null) { if (this.tokenGenerator == null) {
this.tokenGenerator = new RandomTokenGenerator(config.getCoapConfig()); this.tokenGenerator = new RandomTokenGenerator(config.getCoapConfig());
} }
@ -751,8 +690,8 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab
} }
private void cancelExistingObservation(RedisConnection connection, Observation observation, Observation obs, List<Observation> removed) { private void cancelExistingObservation(RedisConnection connection, Observation observation, Observation obs, List<Observation> removed) {
LwM2mPath pathObservation = ((SingleObservation)observation).getPath(); LwM2mPath pathObservation = ((SingleObservation) observation).getPath();
LwM2mPath pathObs = ((SingleObservation)obs).getPath(); LwM2mPath pathObs = ((SingleObservation) obs).getPath();
if ((!pathObservation.equals(pathObs) && pathObs.startWith(pathObservation)) || // pathObservation = "3", pathObs = "3/0/9" if ((!pathObservation.equals(pathObs) && pathObs.startWith(pathObservation)) || // pathObservation = "3", pathObs = "3/0/9"
(pathObservation.equals(pathObs) && !observation.getId().equals(obs.getId()))) { (pathObservation.equals(pathObs) && !observation.getId().equals(obs.getId()))) {
unsafeRemoveObservation(connection, obs.getRegistrationId(), obs.getId().getBytes()); unsafeRemoveObservation(connection, obs.getRegistrationId(), obs.getId().getBytes());
@ -803,7 +742,7 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab
try { try {
schedExecutor.awaitTermination(5, TimeUnit.SECONDS); schedExecutor.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) { } catch (InterruptedException e) {
LOG.warn("Destroying RedisRegistrationStore was interrupted.", e); log.warn("Destroying RedisRegistrationStore was interrupted.", e);
} }
} }
@ -824,7 +763,7 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab
} }
} }
} catch (Exception e) { } catch (Exception e) {
LOG.warn("Unexpected Exception while registration cleaning", e); log.warn("Unexpected Exception while registration cleaning", e);
} }
} }
} }

22
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java

@ -372,21 +372,19 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl
LwM2mPath path = instant.getKey(); LwM2mPath path = instant.getKey();
LwM2mNode node = instant.getValue(); LwM2mNode node = instant.getValue();
LwM2mClient lwM2MClient = clientContext.getClientByEndpoint(registration.getEndpoint()); LwM2mClient lwM2MClient = clientContext.getClientByEndpoint(registration.getEndpoint());
String stringPath = convertObjectIdToVersionedId(path.toString(), lwM2MClient); ObjectModel objectModelVersion = lwM2MClient.getObjectModel(path.toString(), modelProvider);
ObjectModel objectModelVersion = lwM2MClient.getObjectModel(stringPath, modelProvider);
if (objectModelVersion != null) { if (objectModelVersion != null) {
if (node instanceof LwM2mObject) { if (node instanceof LwM2mObject) {
LwM2mObject lwM2mObject = (LwM2mObject) node; LwM2mObject lwM2mObject = (LwM2mObject) node;
this.updateObjectResourceValue(lwM2MClient, lwM2mObject, stringPath, 0); this.updateObjectResourceValue(lwM2MClient, lwM2mObject, path.toString(), 0);
} else if (node instanceof LwM2mObjectInstance) { } else if (node instanceof LwM2mObjectInstance) {
LwM2mObjectInstance lwM2mObjectInstance = (LwM2mObjectInstance) node; LwM2mObjectInstance lwM2mObjectInstance = (LwM2mObjectInstance) node;
this.updateObjectInstanceResourceValue(lwM2MClient, lwM2mObjectInstance, stringPath, 0); this.updateObjectInstanceResourceValue(lwM2MClient, lwM2mObjectInstance, path.toString(), 0);
} else if (node instanceof LwM2mResource) { } else if (node instanceof LwM2mResource) {
LwM2mResource lwM2mResource = (LwM2mResource) node; LwM2mResource lwM2mResource = (LwM2mResource) node;
this.updateResourcesValue(lwM2MClient, lwM2mResource, stringPath, Mode.UPDATE, 0); this.updateResourcesValue(lwM2MClient, lwM2mResource, path.toString(), Mode.UPDATE, 0);
} }
} }
tryAwake(lwM2MClient); tryAwake(lwM2MClient);
} }
} }
@ -569,7 +567,6 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl
} }
private void updateObjectInstanceResourceValue(LwM2mClient client, LwM2mObjectInstance lwM2mObjectInstance, String pathIdVer, int code) { private void updateObjectInstanceResourceValue(LwM2mClient client, LwM2mObjectInstance lwM2mObjectInstance, String pathIdVer, int code) {
LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer));
lwM2mObjectInstance.getResources().forEach((resourceId, resource) -> { lwM2mObjectInstance.getResources().forEach((resourceId, resource) -> {
String pathRez = pathIdVer + "/" + resourceId; String pathRez = pathIdVer + "/" + resourceId;
this.updateResourcesValue(client, resource, pathRez, Mode.UPDATE, code); this.updateResourcesValue(client, resource, pathRez, Mode.UPDATE, code);
@ -584,11 +581,12 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl
* #4 updateAttrTelemetry * #4 updateAttrTelemetry
* @param lwM2MClient - Registration LwM2M Client * @param lwM2MClient - Registration LwM2M Client
* @param lwM2mResource - LwM2mSingleResource response.getContent() * @param lwM2mResource - LwM2mSingleResource response.getContent()
* @param path - resource * @param stringPath - resource
* @param mode - Replace, Update * @param mode - Replace, Update
*/ */
private void updateResourcesValue(LwM2mClient lwM2MClient, LwM2mResource lwM2mResource, String path, Mode mode, int code) { private void updateResourcesValue(LwM2mClient lwM2MClient, LwM2mResource lwM2mResource, String stringPath, Mode mode, int code) {
Registration registration = lwM2MClient.getRegistration(); Registration registration = lwM2MClient.getRegistration();
String path = convertObjectIdToVersionedId(stringPath, lwM2MClient);
if (lwM2MClient.saveResourceValue(path, lwM2mResource, modelProvider, mode)) { if (lwM2MClient.saveResourceValue(path, lwM2mResource, modelProvider, mode)) {
if (path.equals(convertObjectIdToVersionedId(FW_NAME_ID, lwM2MClient))) { if (path.equals(convertObjectIdToVersionedId(FW_NAME_ID, lwM2MClient))) {
otaService.onCurrentFirmwareNameUpdate(lwM2MClient, (String) lwM2mResource.getValue()); otaService.onCurrentFirmwareNameUpdate(lwM2MClient, (String) lwM2mResource.getValue());
@ -632,17 +630,17 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl
* @param registration - Registration LwM2M Client * @param registration - Registration LwM2M Client
*/ */
public void updateAttrTelemetry(Registration registration, String path) { public void updateAttrTelemetry(Registration registration, String path) {
log.trace("UpdateAttrTelemetry paths [{}]", path);
try { try {
ResultsAddKeyValueProto results = this.getParametersFromProfile(registration, path); ResultsAddKeyValueProto results = this.getParametersFromProfile(registration, path);
if (path.equals("/3_1.2/0/9")) {
log.info("UpdateTelemetry paths [{}] key: [{}] value [{}]", path, results.getResultTelemetries().get(0).getKey(), results.getResultTelemetries().get(0).getLongV());
}
SessionInfoProto sessionInfo = this.getSessionInfoOrCloseSession(registration); SessionInfoProto sessionInfo = this.getSessionInfoOrCloseSession(registration);
if (results != null && sessionInfo != null) { if (results != null && sessionInfo != null) {
if (results.getResultAttributes().size() > 0) { if (results.getResultAttributes().size() > 0) {
log.trace("UpdateAttribute paths [{}] value [{}]", path, results.getResultAttributes().get(0).toString());
this.helper.sendParametersOnThingsboardAttribute(results.getResultAttributes(), sessionInfo); this.helper.sendParametersOnThingsboardAttribute(results.getResultAttributes(), sessionInfo);
} }
if (results.getResultTelemetries().size() > 0) { if (results.getResultTelemetries().size() > 0) {
log.trace("UpdateTelemetry paths [{}] value [{}]", path, results.getResultTelemetries().get(0).toString());
this.helper.sendParametersOnThingsboardTelemetry(results.getResultTelemetries(), sessionInfo); this.helper.sendParametersOnThingsboardTelemetry(results.getResultTelemetries(), sessionInfo);
} }
} }

36
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java

@ -156,21 +156,19 @@ public class LwM2MTransportUtil {
} }
public static String convertObjectIdToVersionedId(String path, LwM2mClient lwM2MClient) { public static String convertObjectIdToVersionedId(String path, LwM2mClient lwM2MClient) {
String ver = String.valueOf(lwM2MClient.getSupportedObjectVersion(new LwM2mPath(path).getObjectId())); String[] keyArray = path.split(LWM2M_SEPARATOR_PATH);
return convertObjectIdToVerId(path, ver); if (keyArray.length > 1) {
} try {
public static String convertObjectIdToVerId(String path, String ver) { Integer objectId = Integer.valueOf((keyArray[1].split(LWM2M_SEPARATOR_KEY))[0]);
ver = ver != null ? ver : TbLwM2mVersion.VERSION_1_0.getVersion().toString(); String ver = String.valueOf(lwM2MClient.getSupportedObjectVersion(objectId));
try { ver = ver != null ? ver : TbLwM2mVersion.VERSION_1_0.getVersion().toString();
String[] keyArray = path.split(LWM2M_SEPARATOR_PATH); keyArray[1] = String.valueOf(keyArray[1]).contains(LWM2M_SEPARATOR_KEY) ? keyArray[1] : keyArray[1] + LWM2M_SEPARATOR_KEY + ver;
if (keyArray.length > 1) {
keyArray[1] = keyArray[1] + LWM2M_SEPARATOR_KEY + ver;
return StringUtils.join(keyArray, LWM2M_SEPARATOR_PATH); return StringUtils.join(keyArray, LWM2M_SEPARATOR_PATH);
} else { } catch (Exception e) {
return path; return null;
} }
} catch (Exception e) { } else {
return null; return path;
} }
} }
@ -214,20 +212,20 @@ public class LwM2MTransportUtil {
} }
public static Map<Integer, Object> convertMultiResourceValuesFromRpcBody(Object value, ResourceModel.Type type, String versionedId) throws Exception { public static Map<Integer, Object> convertMultiResourceValuesFromRpcBody(Object value, ResourceModel.Type type, String versionedId) throws Exception {
String valueJsonStr = JacksonUtil.toString(value); String valueJsonStr = JacksonUtil.toString(value);
JsonElement element = JsonUtils.parse(valueJsonStr); JsonElement element = JsonUtils.parse(valueJsonStr);
return convertMultiResourceValuesFromJson(element, type, versionedId); return convertMultiResourceValuesFromJson(element, type, versionedId);
} }
public static Map<Integer, Object> convertMultiResourceValuesFromJson(JsonElement newValProto, ResourceModel.Type type, String versionedId) { public static Map<Integer, Object> convertMultiResourceValuesFromJson(JsonElement newValProto, ResourceModel.Type type, String versionedId) {
Map<Integer, Object> newValues = new HashMap<>(); Map<Integer, Object> newValues = new HashMap<>();
newValProto.getAsJsonObject().entrySet().forEach((obj) -> { newValProto.getAsJsonObject().entrySet().forEach((obj) -> {
newValues.put(Integer.valueOf(obj.getKey()), convertValueByTypeResource (obj.getValue().getAsString(), type, versionedId)); newValues.put(Integer.valueOf(obj.getKey()), convertValueByTypeResource(obj.getValue().getAsString(), type, versionedId));
}); });
return newValues; return newValues;
} }
public static Object convertValueByTypeResource (String value, ResourceModel.Type type, String versionedId) { public static Object convertValueByTypeResource(String value, ResourceModel.Type type, String versionedId) {
return LwM2mValueConverterImpl.getInstance().convertValue(value, return LwM2mValueConverterImpl.getInstance().convertValue(value,
STRING, type, new LwM2mPath(fromVersionedIdToObjectId(versionedId))); STRING, type, new LwM2mPath(fromVersionedIdToObjectId(versionedId)));
} }
@ -356,7 +354,7 @@ public class LwM2MTransportUtil {
serverCoapConfig.setTransient(DTLS_CONNECTION_ID_LENGTH); serverCoapConfig.setTransient(DTLS_CONNECTION_ID_LENGTH);
serverCoapConfig.setTransient(DTLS_CONNECTION_ID_NODE_ID); serverCoapConfig.setTransient(DTLS_CONNECTION_ID_NODE_ID);
serverCoapConfig.set(DTLS_CONNECTION_ID_LENGTH, cIdLength); serverCoapConfig.set(DTLS_CONNECTION_ID_LENGTH, cIdLength);
if ( cIdLength > 4) { if (cIdLength > 4) {
serverCoapConfig.set(DTLS_CONNECTION_ID_NODE_ID, 0); serverCoapConfig.set(DTLS_CONNECTION_ID_NODE_ID, 0);
} else { } else {
serverCoapConfig.set(DTLS_CONNECTION_ID_NODE_ID, null); serverCoapConfig.set(DTLS_CONNECTION_ID_NODE_ID, null);

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

Loading…
Cancel
Save