Merge branch 'master' into fix/4785-codeblocks-size

This commit is contained in:
Max Petrov 2024-11-22 18:13:50 +02:00 committed by GitHub
commit 0f1b546ffe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 357 additions and 83 deletions

View File

@ -548,7 +548,7 @@
"id": "units",
"name": "{i18n:scada.symbol.units}",
"type": "units",
"default": "°C",
"default": "°C",
"required": null,
"subLabel": null,
"divider": null,

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -548,7 +548,7 @@
"id": "units",
"name": "{i18n:scada.symbol.units}",
"type": "units",
"default": "°C",
"default": "°C",
"required": null,
"subLabel": null,
"divider": null,

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -540,7 +540,7 @@
"id": "units",
"name": "{i18n:scada.symbol.units}",
"type": "units",
"default": "°C",
"default": "°C",
"required": null,
"subLabel": null,
"divider": null,

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -540,7 +540,7 @@
"id": "units",
"name": "{i18n:scada.symbol.units}",
"type": "units",
"default": "°C",
"default": "°C",
"required": null,
"subLabel": null,
"divider": null,

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -1,5 +1,4 @@
<svg width="600" height="1e3" fill="none" version="1.1" viewBox="0 0 600 1e3" xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg">
<tb:metadata><![CDATA[{
<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[{
"title": "HP Vertical tank",
"description": "Vertical tank",
"searchTags": [
@ -33,12 +32,12 @@
},
{
"tag": "fluid-background",
"stateRenderFunction": " 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))*970; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n element.attr({height: height});\n }",
"stateRenderFunction": " 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))*994; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n element.attr({height: height});\n }",
"actions": null
},
{
"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 = 15;\n var majorIntervalLength = 970 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(160, y, 192, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 150, 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 \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(172, minorY, 192, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 3;\n var majorIntervalLength = 994 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(160, y, 192, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n if (i === 0) {\n majorTickText.attr({x: 150, y: y + 10, 'text-anchor': 'end', class: 'majorTickText'});\n } else if (i === majorIntervals) {\n majorTickText.attr({x: 150, y: y - 5, 'text-anchor': 'end', class: 'majorTickText'});\n } else {\n majorTickText.attr({x: 150, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n }\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(172, minorY, 192, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}",
"actions": null
},
{
@ -455,16 +454,12 @@
}
]
}]]></tb:metadata>
<path d="m0 120s0-120 140-120c129 3.4657e-5 200.5-1.0591e-4 320 0 140 1.2408e-4 140 120 140 120v760s0 120-140 120h-320c-140 0-140-120-140-120v-760z" fill="#EBEBEB" tb:tag="background"/>
<path d="m0.936 120h0.064l1e-5 -3e-3 5e-5 -0.014 4.3e-4 -0.061c5.1e-4 -0.054 0.00153-0.137 0.00361-0.247 0.00415-0.221 0.0125-0.551 0.02932-0.984 0.03365-0.865 0.10115-2.139 0.23656-3.764 0.27086-3.25 0.81325-7.902 1.8991-13.486 2.1725-11.173 6.5161-26.06 15.195-40.937 17.319-29.69 51.988-59.504 121.64-59.504l141.99-2e-5 178.01 2e-5c69.648 6e-5 104.32 29.814 121.64 59.504 8.679 14.878 13.022 29.764 15.195 40.937 1.086 5.585 1.628 10.236 1.899 13.486 0.135 1.625 0.203 2.899 0.237 3.764 0.016 0.433 0.025 0.763 0.029 0.984 1e-3 0.047 1e-3 0.09 2e-3 0.127 1e-3 0.049 1e-3 0.089 2e-3 0.12v0.078h0.064-0.064v760h0.064-0.064v0.078c-1e-3 0.054-2e-3 0.137-4e-3 0.247-4e-3 0.221-0.013 0.551-0.029 0.984-0.034 0.865-0.102 2.139-0.237 3.764-0.271 3.25-0.813 7.902-1.899 13.486-2.173 11.173-6.516 26.06-15.195 40.937-17.319 29.69-51.988 59.504-121.64 59.504h-320c-69.648 0-104.32-29.814-121.64-59.504-8.6786-14.877-13.022-29.764-15.195-40.937-1.0859-5.584-1.6283-10.236-1.8991-13.486-0.13541-1.625-0.20291-2.899-0.23656-3.764-0.01682-0.433-0.02517-0.763-0.02932-0.984-0.00208-0.11-0.0031-0.193-0.00361-0.247l-4.3e-4 -0.061-5e-5 -0.014-1e-5 -3e-3h-0.064 0.064v-760h-0.064z" stroke="#000" stroke-opacity=".87" stroke-width="2"/>
<mask id="mask0_3610_179351" x="2" y="2" width="596" height="996" style="mask-type:alpha" maskUnits="userSpaceOnUse">
<path d="m0 120s0-120 140-120c129 3.4657e-5 200.5-1.0591e-4 320 0 140 1.2408e-4 140 120 140 120v760s0 120-140 120h-320c-140 0-140-120-140-120v-760z" fill="#EBEBEB" tb:tag="background"/><path d="m0.936 120h0.064l1e-5 -3e-3 5e-5 -0.014 4.3e-4 -0.061c5.1e-4 -0.054 0.00153-0.137 0.00361-0.247 0.00415-0.221 0.0125-0.551 0.02932-0.984 0.03365-0.865 0.10115-2.139 0.23656-3.764 0.27086-3.25 0.81325-7.902 1.8991-13.486 2.1725-11.173 6.5161-26.06 15.195-40.937 17.319-29.69 51.988-59.504 121.64-59.504l141.99-2e-5 178.01 2e-5c69.648 6e-5 104.32 29.814 121.64 59.504 8.679 14.878 13.022 29.764 15.195 40.937 1.086 5.585 1.628 10.236 1.899 13.486 0.135 1.625 0.203 2.899 0.237 3.764 0.016 0.433 0.025 0.763 0.029 0.984 1e-3 0.047 1e-3 0.09 2e-3 0.127 1e-3 0.049 1e-3 0.089 2e-3 0.12v0.078h0.064-0.064v760h0.064-0.064v0.078c-1e-3 0.054-2e-3 0.137-4e-3 0.247-4e-3 0.221-0.013 0.551-0.029 0.984-0.034 0.865-0.102 2.139-0.237 3.764-0.271 3.25-0.813 7.902-1.899 13.486-2.173 11.173-6.516 26.06-15.195 40.937-17.319 29.69-51.988 59.504-121.64 59.504h-320c-69.648 0-104.32-29.814-121.64-59.504-8.6786-14.877-13.022-29.764-15.195-40.937-1.0859-5.584-1.6283-10.236-1.8991-13.486-0.13541-1.625-0.20291-2.899-0.23656-3.764-0.01682-0.433-0.02517-0.763-0.02932-0.984-0.00208-0.11-0.0031-0.193-0.00361-0.247l-4.3e-4 -0.061-5e-5 -0.014-1e-5 -3e-3h-0.064 0.064v-760h-0.064z" stroke="#000" stroke-opacity=".87" stroke-width="2"/><mask id="mask0_3610_179351" x="2" y="2" width="596" height="996" style="mask-type:alpha" maskUnits="userSpaceOnUse">
<path d="m2 121.52s0-119.52 139.07-119.52c128.14 3e-5 199.16-1.1e-4 317.87 0 139.07 1.2e-4 139.07 119.52 139.07 119.52v756.96s0 119.52-139.07 119.52h-317.87c-139.07 0-139.07-119.52-139.07-119.52v-756.96z" fill="#EBEBEB"/>
<path d="m2.936 121.52h0.064l1e-5 -3e-3 5e-5 -0.014 4.3e-4 -0.06c5e-4 -0.055 0.00152-0.137 0.00358-0.247 0.00412-0.22 0.01242-0.549 0.02913-0.979 0.03342-0.862 0.10047-2.131 0.23498-3.75 0.26905-3.237 0.80781-7.869 1.8864-13.432 2.158-11.128 6.4726-25.954 15.093-40.772 17.203-29.57 51.639-59.263 120.82-59.263l141.05-2e-5 176.82 2e-5c69.18 6e-5 103.62 29.693 120.82 59.263 8.621 14.818 12.935 29.644 15.093 40.772 1.079 5.563 1.618 10.195 1.887 13.432 0.134 1.619 0.201 2.888 0.235 3.75 0.017 0.43 0.025 0.759 0.029 0.979 2e-3 0.11 3e-3 0.192 4e-3 0.247v0.077h0.064-0.064v756.96h0.122-0.122v0.077c-1e-3 0.055-2e-3 0.137-4e-3 0.247-4e-3 0.22-0.012 0.549-0.029 0.979-0.034 0.862-0.101 2.131-0.235 3.75-0.269 3.237-0.808 7.869-1.887 13.432-2.158 11.128-6.472 25.954-15.093 40.772-17.203 29.57-51.639 59.263-120.82 59.263h-317.87c-69.18 0-103.62-29.693-120.82-59.263-8.6205-14.818-12.935-29.644-15.093-40.772-1.0786-5.563-1.6174-10.195-1.8864-13.432-0.13451-1.619-0.20156-2.888-0.23498-3.75-0.01671-0.43-0.02501-0.759-0.02913-0.979-0.00206-0.11-0.00308-0.192-0.00358-0.247l-4.3e-4 -0.06-5e-5 -0.014-1e-5 -3e-3h-0.064 0.064v-756.96h-0.064z" stroke="#000" stroke-opacity=".87" stroke-width="2"/>
</mask>
<g mask="url(#mask0_3610_179351)">
</mask><g mask="url(#mask0_3610_179351)">
<rect transform="scale(1,-1)" x="2" y="-995" width="600" height="419" fill="#c8dff7" tb:tag="fluid-background"/>
</g>
<g tb:tag="scale">
</g><g tb:tag="scale">
<line x1="162" x2="194" y1="3.5" y2="3.5" stroke="#000" stroke-opacity=".38" stroke-width="3"/>
<line x1="174" x2="194" y1="23.359" y2="23.359" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
<line x1="174" x2="194" y1="43.221" y2="43.221" stroke="#000" stroke-opacity=".12" stroke-width="3"/>
@ -527,18 +522,15 @@
<path d="m139.2 798.95v2.25h-11.437v-1.933l5.554-6.059c0.61-0.687 1.09-1.281 1.442-1.781 0.351-0.5 0.598-0.949 0.738-1.348 0.149-0.406 0.223-0.801 0.223-1.183 0-0.54-0.102-1.012-0.305-1.418-0.195-0.415-0.484-0.739-0.867-0.973-0.383-0.242-0.848-0.363-1.395-0.363-0.632 0-1.164 0.136-1.593 0.41-0.43 0.273-0.754 0.652-0.973 1.137-0.219 0.476-0.328 1.023-0.328 1.64h-2.824c0-0.992 0.226-1.898 0.679-2.719 0.453-0.828 1.11-1.484 1.969-1.968 0.86-0.493 1.895-0.739 3.106-0.739 1.14 0 2.109 0.192 2.906 0.575 0.797 0.382 1.402 0.925 1.816 1.628 0.422 0.704 0.633 1.536 0.633 2.497 0 0.531-0.086 1.058-0.258 1.582-0.172 0.523-0.418 1.046-0.738 1.57-0.313 0.516-0.684 1.035-1.113 1.558-0.43 0.516-0.903 1.04-1.418 1.571l-3.692 4.066zm13.575-7.711v2.789c0 1.336-0.133 2.477-0.399 3.422-0.258 0.938-0.633 1.699-1.125 2.285s-1.082 1.016-1.769 1.289c-0.68 0.274-1.442 0.411-2.286 0.411-0.671 0-1.296-0.086-1.875-0.258-0.57-0.172-1.086-0.442-1.546-0.809-0.461-0.367-0.856-0.84-1.184-1.418-0.32-0.586-0.57-1.285-0.75-2.097-0.172-0.813-0.258-1.754-0.258-2.825v-2.789c0-1.343 0.133-2.476 0.399-3.398 0.265-0.93 0.644-1.684 1.136-2.262 0.492-0.586 1.078-1.012 1.758-1.277 0.688-0.266 1.453-0.399 2.297-0.399 0.68 0 1.305 0.086 1.875 0.258 0.578 0.164 1.094 0.426 1.547 0.785 0.461 0.36 0.851 0.828 1.172 1.407 0.328 0.57 0.578 1.261 0.75 2.074 0.172 0.804 0.258 1.742 0.258 2.812zm-2.825 3.188v-3.61c0-0.679-0.039-1.277-0.117-1.793-0.078-0.523-0.195-0.964-0.351-1.324-0.149-0.367-0.336-0.664-0.563-0.89-0.226-0.235-0.484-0.403-0.773-0.504-0.289-0.11-0.614-0.164-0.973-0.164-0.438 0-0.828 0.086-1.172 0.257-0.344 0.164-0.633 0.43-0.867 0.797s-0.414 0.852-0.539 1.453c-0.117 0.594-0.176 1.317-0.176 2.168v3.61c0 0.687 0.039 1.293 0.117 1.816 0.078 0.524 0.196 0.973 0.352 1.348 0.156 0.367 0.344 0.672 0.562 0.914 0.227 0.234 0.485 0.406 0.774 0.516 0.297 0.109 0.621 0.164 0.972 0.164 0.446 0 0.84-0.086 1.184-0.258s0.633-0.446 0.867-0.821c0.235-0.382 0.41-0.878 0.528-1.488 0.117-0.609 0.175-1.34 0.175-2.191z" fill="#000" fill-opacity=".38"/>
<path d="m135.47 881.48v17.121h-2.824v-13.77l-4.184 1.418v-2.332l6.668-2.437zm17.301 7.16v2.789c0 1.336-0.133 2.476-0.399 3.422-0.258 0.937-0.633 1.699-1.125 2.285s-1.082 1.015-1.769 1.289c-0.68 0.273-1.442 0.41-2.286 0.41-0.671 0-1.296-0.086-1.875-0.258-0.57-0.172-1.086-0.441-1.546-0.808-0.461-0.368-0.856-0.84-1.184-1.418-0.32-0.586-0.57-1.286-0.75-2.098-0.172-0.813-0.258-1.754-0.258-2.824v-2.789c0-1.344 0.133-2.477 0.399-3.399 0.265-0.929 0.644-1.683 1.136-2.261 0.492-0.586 1.078-1.012 1.758-1.278 0.688-0.265 1.453-0.398 2.297-0.398 0.68 0 1.305 0.086 1.875 0.258 0.578 0.164 1.094 0.425 1.547 0.785 0.461 0.359 0.851 0.828 1.172 1.406 0.328 0.57 0.578 1.262 0.75 2.074 0.172 0.805 0.258 1.742 0.258 2.813zm-2.825 3.187v-3.609c0-0.68-0.039-1.278-0.117-1.793-0.078-0.524-0.195-0.965-0.351-1.324-0.149-0.368-0.336-0.664-0.563-0.891-0.226-0.234-0.484-0.402-0.773-0.504-0.289-0.109-0.614-0.164-0.973-0.164-0.438 0-0.828 0.086-1.172 0.258-0.344 0.164-0.633 0.43-0.867 0.797s-0.414 0.851-0.539 1.453c-0.117 0.594-0.176 1.316-0.176 2.168v3.609c0 0.688 0.039 1.293 0.117 1.817 0.078 0.523 0.196 0.972 0.352 1.347 0.156 0.367 0.344 0.672 0.562 0.914 0.227 0.235 0.485 0.407 0.774 0.516 0.297 0.109 0.621 0.164 0.972 0.164 0.446 0 0.84-0.086 1.184-0.258s0.633-0.445 0.867-0.82c0.235-0.383 0.41-0.879 0.528-1.488 0.117-0.61 0.175-1.34 0.175-2.192z" fill="#000" fill-opacity=".38"/>
<path d="m152.77 986.04v2.789c0 1.336-0.133 2.477-0.399 3.422-0.258 0.938-0.633 1.699-1.125 2.285s-1.082 1.016-1.769 1.289c-0.68 0.274-1.442 0.41-2.286 0.41-0.671 0-1.296-0.086-1.875-0.257-0.57-0.172-1.086-0.442-1.546-0.809-0.461-0.367-0.856-0.84-1.184-1.418-0.32-0.586-0.57-1.285-0.75-2.098-0.172-0.812-0.258-1.754-0.258-2.824v-2.789c0-1.344 0.133-2.477 0.399-3.398 0.265-0.93 0.644-1.684 1.136-2.262 0.492-0.586 1.078-1.012 1.758-1.277 0.688-0.266 1.453-0.399 2.297-0.399 0.68 0 1.305 0.086 1.875 0.258 0.578 0.164 1.094 0.426 1.547 0.785 0.461 0.359 0.851 0.828 1.172 1.406 0.328 0.571 0.578 1.262 0.75 2.075 0.172 0.804 0.258 1.742 0.258 2.812zm-2.825 3.188v-3.61c0-0.679-0.039-1.277-0.117-1.793-0.078-0.523-0.195-0.965-0.351-1.324-0.149-0.367-0.336-0.664-0.563-0.891-0.226-0.234-0.484-0.402-0.773-0.504-0.289-0.109-0.614-0.164-0.973-0.164-0.438 0-0.828 0.086-1.172 0.258-0.344 0.164-0.633 0.43-0.867 0.797s-0.414 0.852-0.539 1.453c-0.117 0.594-0.176 1.317-0.176 2.168v3.61c0 0.687 0.039 1.293 0.117 1.816s0.196 0.973 0.352 1.348c0.156 0.367 0.344 0.671 0.562 0.914 0.227 0.234 0.485 0.406 0.774 0.515 0.297 0.11 0.621 0.164 0.972 0.164 0.446 0 0.84-0.086 1.184-0.257 0.344-0.172 0.633-0.446 0.867-0.821 0.235-0.383 0.41-0.879 0.528-1.488 0.117-0.609 0.175-1.34 0.175-2.191z" fill="#000" fill-opacity=".38"/>
</g>
<path d="m201.79 0s-201.79 0-201.79 167.5v820.9c0 6.628 5.3727 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 7h43.998c3.866 0 7-3.134 7-7v-751c0-3.866-3.134-7-7-7z" fill="#000" fill-opacity="0" style="stroke-width:.55902" tb:tag="clickArea"/>
<g fill="#d12730" style="display:none" tb:tag="critical">
</g><path d="m201.79 0s-201.79 0-201.79 167.5v820.9c0 6.628 5.3727 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 7h43.998c3.866 0 7-3.134 7-7v-751c0-3.866-3.134-7-7-7z" fill="#000" fill-opacity="0" style="stroke-width:.55902" tb:tag="clickArea"/><g fill="#d12730" style="display: none;" tb:tag="critical">
<rect width="84" height="84" rx="4" fill="#fff" style=""/>
<rect width="84" height="84" rx="4" style=""/>
<rect x="2" y="2" width="80" height="80" rx="2" stroke="#000" stroke-opacity=".87" stroke-width="4" style=""/>
<path d="m44.559 27.562-0.4688 20.059h-4.0234l-0.4883-20.059zm-5.1172 26.211c0-0.7161 0.2344-1.3151 0.7031-1.7968 0.4818-0.4948 1.1459-0.7422 1.9922-0.7422 0.8334 0 1.4909 0.2474 1.9727 0.7422 0.4817 0.4817 0.7226 1.0807 0.7226 1.7968 0 0.6901-0.2409 1.2826-0.7226 1.7774-0.4818 0.4818-1.1393 0.7226-1.9727 0.7226-0.8463 0-1.5104-0.2408-1.9922-0.7226-0.4687-0.4948-0.7031-1.0873-0.7031-1.7774z" fill="#000" fill-opacity=".87" style=""/>
</g>
<g transform="translate(0,-4.9442)" fill="#faa405" style="display:none" tb:tag="warning">
</g><g transform="translate(0,-4.9442)" fill="#faa405" style="display: none;" tb:tag="warning">
<path d="m38.422 7.1554c1.4741-2.9482 5.6813-2.9482 7.1554 0l35.528 71.056c1.3298 2.6596-0.6042 5.7889-3.5777 5.7889h-71.056c-2.9735 0-4.9075-3.1292-3.5777-5.7889z" fill="#fff" style=""/>
<path d="m38.422 7.1554c1.4741-2.9482 5.6813-2.9482 7.1554 0l35.528 71.056c1.3298 2.6596-0.6042 5.7889-3.5777 5.7889h-71.056c-2.9735 0-4.9075-3.1292-3.5777-5.7889z" style=""/>
<path d="m40.211 8.0498c0.7371-1.4741 2.8407-1.4741 3.5778-1e-5l35.528 71.056c0.6649 1.3298-0.3021 2.8944-1.7888 2.8944h-71.056c-1.4868 0-2.4538-1.5646-1.7889-2.8944z" stroke="#000" stroke-opacity=".87" stroke-width="4" style=""/>
<path d="m44.559 37.562-0.4688 20.059h-4.0234l-0.4883-20.059zm-5.1172 26.211c0-0.7161 0.2344-1.3151 0.7031-1.7968 0.4818-0.4948 1.1459-0.7422 1.9922-0.7422 0.8334 0 1.4909 0.2474 1.9727 0.7422 0.4817 0.4817 0.7226 1.0807 0.7226 1.7968 0 0.6901-0.2409 1.2826-0.7226 1.7774-0.4818 0.4818-1.1393 0.7226-1.9727 0.7226-0.8463 0-1.5104-0.2408-1.9922-0.7226-0.4687-0.4948-0.7031-1.0873-0.7031-1.7774z" fill="#000" fill-opacity=".87" style=""/>
</g>
</svg>
</svg>

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View File

@ -59,10 +59,6 @@ $$
ALTER TABLE mobile_app_oauth2_client RENAME TO mobile_app_bundle_oauth2_client;
ALTER TABLE mobile_app_bundle_oauth2_client DROP CONSTRAINT IF EXISTS fk_domain;
ALTER TABLE mobile_app_bundle_oauth2_client RENAME COLUMN mobile_app_id TO mobile_app_bundle_id;
IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'fk_mobile_app_bundle_oauth2_client_bundle_id') THEN
ALTER TABLE mobile_app_bundle_oauth2_client ADD CONSTRAINT fk_mobile_app_bundle_oauth2_client_bundle_id
FOREIGN KEY (mobile_app_bundle_id) REFERENCES mobile_app_bundle(id) ON DELETE CASCADE;
END IF;
END IF;
END;
$$;
@ -98,6 +94,10 @@ $$
UPDATE mobile_app_bundle_oauth2_client SET mobile_app_bundle_id = generatedBundleId WHERE mobile_app_bundle_id = mobileAppRecord.id;
END LOOP;
END IF;
IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'fk_mobile_app_bundle_oauth2_client_bundle_id') THEN
ALTER TABLE mobile_app_bundle_oauth2_client ADD CONSTRAINT fk_mobile_app_bundle_oauth2_client_bundle_id
FOREIGN KEY (mobile_app_bundle_id) REFERENCES mobile_app_bundle(id) ON DELETE CASCADE;
END IF;
ALTER TABLE mobile_app DROP COLUMN IF EXISTS oauth2_enabled;
IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'mobile_app_pkg_name_platform_unq_key') THEN
ALTER TABLE mobile_app ADD CONSTRAINT mobile_app_pkg_name_platform_unq_key UNIQUE (pkg_name, platform_type);
@ -155,7 +155,7 @@ $$
iosAppId := uuid_generate_v4();
INSERT INTO mobile_app(id, created_time, tenant_id, pkg_name, platform_type, status, store_info)
VALUES (iosAppId, (extract(epoch from now()) * 1000), qrCodeRecord.tenant_id,
iosPkgName, 'IOS', 'DRAFT', qrCodeRecord.ios_config);
iosPkgName, 'IOS', 'DRAFT', qrCodeRecord.ios_config::jsonb - 'enabled');
IF generatedBundleId IS NULL THEN
generatedBundleId := uuid_generate_v4();
INSERT INTO mobile_app_bundle(id, created_time, tenant_id, title, ios_app_id)

View File

@ -15,21 +15,16 @@
*/
package org.thingsboard.server.service.subscription;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.query.AlarmCountQuery;
import org.thingsboard.server.common.data.query.OriginatorAlarmFilter;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.ws.WebSocketService;
import org.thingsboard.server.service.ws.WebSocketSessionRef;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmStatusCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmStatusUpdate;
import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate;
import java.util.List;
@ -82,7 +77,10 @@ public class TbAlarmStatusSubCtx extends TbAbstractSubCtx {
}
public void sendUpdate() {
sendWsMsg(subscription.createUpdate());
sendWsMsg(AlarmStatusUpdate.builder()
.cmdId(cmdId)
.active(subscription.hasAlarms())
.build());
}
public void fetchActiveAlarms() {

View File

@ -22,7 +22,6 @@ import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmStatusUpdate;
import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate;
import java.util.HashSet;
@ -53,15 +52,12 @@ public class TbAlarmStatusSubscription extends TbSubscription<AlarmSubscriptionU
this.severityList = severityList;
}
public AlarmStatusUpdate createUpdate() {
return AlarmStatusUpdate.builder()
.cmdId(getSubscriptionId())
.active(!alarmIds.isEmpty())
.build();
}
public boolean matches(AlarmInfo alarm) {
return !alarm.isCleared() && (this.typeList == null || this.typeList.contains(alarm.getType())) &&
(this.severityList == null || this.severityList.contains(alarm.getSeverity()));
}
public boolean hasAlarms() {
return !alarmIds.isEmpty();
}
}

View File

@ -38,6 +38,7 @@ import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUnsubscribeCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUnsubscribeCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmStatusUnsubscribeCmd;
import java.util.List;
@ -67,6 +68,7 @@ public class WsCommandsWrapper {
@Type(name = "ENTITY_DATA_UNSUBSCRIBE", value = EntityDataUnsubscribeCmd.class),
@Type(name = "ENTITY_COUNT_UNSUBSCRIBE", value = EntityCountUnsubscribeCmd.class),
@Type(name = "NOTIFICATIONS_UNSUBSCRIBE", value = NotificationsUnsubCmd.class),
@Type(name = "ALARM_STATUS_UNSUBSCRIBE", value = AlarmStatusUnsubscribeCmd.class),
})
private List<WsCmd> cmds;

View File

@ -22,6 +22,9 @@ import {
AlarmDataCmd,
AlarmDataUnsubscribeCmd,
AlarmDataUpdate,
AlarmStatusCmd,
AlarmStatusUnsubscribeCmd,
AlarmStatusUpdate,
EntityCountCmd,
EntityCountUnsubscribeCmd,
EntityCountUpdate,
@ -30,6 +33,7 @@ import {
EntityDataUpdate,
isAlarmCountUpdateMsg,
isAlarmDataUpdateMsg,
isAlarmStatusUpdateMsg,
isEntityCountUpdateMsg,
isEntityDataUpdateMsg,
isNotificationCountUpdateMsg,
@ -121,6 +125,10 @@ export class TelemetryWebsocketService extends WebsocketService<TelemetrySubscri
const alarmCountUnsubscribeCmd = new AlarmCountUnsubscribeCmd();
alarmCountUnsubscribeCmd.cmdId = subscriptionCommand.cmdId;
this.cmdWrapper.cmds.push(alarmCountUnsubscribeCmd);
} else if (subscriptionCommand instanceof AlarmStatusCmd) {
const alarmCountUnsubscribeCmd = new AlarmStatusUnsubscribeCmd();
alarmCountUnsubscribeCmd.cmdId = subscriptionCommand.cmdId;
this.cmdWrapper.cmds.push(alarmCountUnsubscribeCmd);
} else if (subscriptionCommand instanceof UnreadCountSubCmd || subscriptionCommand instanceof UnreadSubCmd) {
const notificationsUnsubCmds = new UnsubscribeCmd();
notificationsUnsubCmds.cmdId = subscriptionCommand.cmdId;
@ -157,6 +165,8 @@ export class TelemetryWebsocketService extends WebsocketService<TelemetrySubscri
subscriber.onEntityCount(new EntityCountUpdate(message));
} else if (isAlarmCountUpdateMsg(message)) {
subscriber.onAlarmCount(new AlarmCountUpdate(message));
} else if (isAlarmStatusUpdateMsg(message)) {
subscriber.onAlarmStatus(new AlarmStatusUpdate(message))
}
}
} else if ('subscriptionId' in message && message.subscriptionId) {

View File

@ -105,14 +105,14 @@ export class AuditLogDetailsDialogComponent extends DialogComponent<AuditLogDeta
let newWidth = 600;
if (content && content.length > 0) {
const lines = content.split('\n');
newHeight = 16 * lines.length + 16;
newHeight = 17 * lines.length + 16;
let maxLineLength = 0;
lines.forEach((row) => {
const line = row.replace(/\t/g, ' ').replace(/\n/g, '');
const lineLength = line.length;
maxLineLength = Math.max(maxLineLength, lineLength);
});
newWidth = 8 * maxLineLength + 16;
newWidth = 9 * maxLineLength + 16;
}
// newHeight = Math.min(400, newHeight);
this.renderer.setStyle(editorElement, 'minHeight', newHeight.toString() + 'px');

View File

@ -139,14 +139,14 @@ export class EventContentDialogComponent extends DialogComponent<EventContentDia
let newWidth = 600;
if (content && content.length > 0) {
const lines = content.split('\n');
newHeight = 16 * lines.length + 16;
newHeight = 17 * lines.length + 16;
let maxLineLength = 0;
lines.forEach((row) => {
const line = row.replace(/\t/g, ' ').replace(/\n/g, '');
const lineLength = line.length;
maxLineLength = Math.max(maxLineLength, lineLength);
});
newWidth = 8 * maxLineLength + 16;
newWidth = 9 * maxLineLength + 16;
}
// newHeight = Math.min(400, newHeight);
this.renderer.setStyle(editorElement, 'minHeight', newHeight.toString() + 'px');

View File

@ -242,6 +242,8 @@ export abstract class ValueGetter<V> extends ValueAction {
return new AttributeValueGetter<V>(ctx, settings, valueType, valueObserver, simulated);
case GetValueAction.GET_TIME_SERIES:
return new TimeSeriesValueGetter<V>(ctx, settings, valueType, valueObserver, simulated);
case GetValueAction.GET_ALARM_STATUS:
return new AlarmStatusValueGetter<V>(ctx, settings, valueType, valueObserver, simulated);
case GetValueAction.GET_DASHBOARD_STATE:
return new DashboardStateGetter<V>(ctx, settings, valueType, valueObserver, simulated);
}
@ -257,7 +259,7 @@ export abstract class ValueGetter<V> extends ValueAction {
protected valueObserver: Partial<Observer<V>>,
protected simulated: boolean) {
super(ctx, settings);
if (this.settings.action !== GetValueAction.DO_NOTHING) {
if (this.settings.action !== GetValueAction.DO_NOTHING && this.settings.action !== GetValueAction.GET_ALARM_STATUS) {
this.dataConverter = new DataToValueConverter<V>(settings.dataToValue, valueType);
}
}
@ -537,6 +539,58 @@ export class TimeSeriesValueGetter<V> extends TelemetryValueGetter<V, TelemetryV
}
}
export class AlarmStatusValueGetter<V> extends ValueGetter<V> {
protected targetEntityId: EntityId;
private telemetrySubscriber: SharedTelemetrySubscriber;
constructor(protected ctx: WidgetContext,
protected settings: GetValueSettings<V>,
protected valueType: ValueType,
protected valueObserver: Partial<Observer<V>>,
protected simulated: boolean) {
super(ctx, settings, valueType, valueObserver, simulated);
const entityInfo = this.ctx.defaultSubscription.getFirstEntityInfo();
this.targetEntityId = entityInfo?.entityId;
}
protected doGetValue(): Observable<boolean> {
if (this.simulated) {
return of(false).pipe(delay(100));
} else {
if (!this.targetEntityId && !this.ctx.defaultSubscription.rpcEnabled) {
return throwError(() => new Error(this.ctx.translate.instant('widgets.value-action.error.target-entity-is-not-set')));
}
if (this.targetEntityId) {
return this.subscribeForTelemetryValue();
} else {
return of(null);
}
}
}
private subscribeForTelemetryValue(): Observable<boolean> {
this.telemetrySubscriber =
SharedTelemetrySubscriber.createAlarmStatusSubscription(this.ctx.telemetryWsService, this.targetEntityId,
this.ctx.ngZone, this.settings.getAlarmStatus.severityList, this.settings.getAlarmStatus.typeList);
this.telemetrySubscriber.subscribe();
return this.telemetrySubscriber.alarmStatus$.pipe(
map((data) => {
return data.active;
})
);
}
destroy() {
if (this.telemetrySubscriber) {
this.telemetrySubscriber.unsubscribe();
this.telemetrySubscriber = null;
}
super.destroy();
}
}
export class DashboardStateGetter<V> extends ValueGetter<V> {
constructor(protected ctx: WidgetContext,
protected settings: GetValueSettings<V>,

View File

@ -38,6 +38,10 @@ export const actionButtonDefaultSettings: ActionButtonWidgetSettings = {
getTimeSeries: {
key: 'state'
},
getAlarmStatus: {
severityList: null,
typeList: null
},
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,
@ -54,6 +58,10 @@ export const actionButtonDefaultSettings: ActionButtonWidgetSettings = {
getTimeSeries: {
key: 'state'
},
getAlarmStatus: {
severityList: null,
typeList: null
},
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,

View File

@ -62,6 +62,10 @@ export const commandButtonDefaultSettings: CommandButtonWidgetSettings = {
getTimeSeries: {
key: 'state'
},
getAlarmStatus: {
severityList: null,
typeList: null
},
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,

View File

@ -61,6 +61,10 @@ export const toggleButtonDefaultSettings: ToggleButtonWidgetSettings = {
getTimeSeries: {
key: 'state'
},
getAlarmStatus: {
severityList: null,
typeList: null
},
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,
@ -77,6 +81,10 @@ export const toggleButtonDefaultSettings: ToggleButtonWidgetSettings = {
getTimeSeries: {
key: 'state'
},
getAlarmStatus: {
severityList: null,
typeList: null
},
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,

View File

@ -145,6 +145,7 @@ export class SignalStrengthWidgetComponent implements OnInit, OnDestroy, AfterVi
private rssi = -100;
private noSignal = false;
private noData = false;
private noSignalRssiValue = -100;
constructor(public widgetComponent: WidgetComponent,
private imagePipe: ImagePipe,
@ -166,6 +167,8 @@ export class SignalStrengthWidgetComponent implements OnInit, OnDestroy, AfterVi
this.dateStyle.color = this.settings.dateColor;
}
this.noSignalRssiValue = this.settings.noSignalRssiValue ?? -100;
this.activeBarsColor = ColorProcessor.fromSettings(this.settings.activeBarsColor);
const inactiveBarsColor = tinycolor(this.settings.inactiveBarsColor);
this.inactiveBarsColorHex = inactiveBarsColor.toHexString();
@ -262,7 +265,7 @@ export class SignalStrengthWidgetComponent implements OnInit, OnDestroy, AfterVi
}
}
this.noSignal = this.rssi <= this.settings.noSignalRssiValue;
this.noSignal = this.rssi <= this.noSignalRssiValue;
this.activeBarsColor.update(this.rssi);
@ -342,7 +345,7 @@ export class SignalStrengthWidgetComponent implements OnInit, OnDestroy, AfterVi
const activeBarsOpacity = activeBarsColor.getAlpha();
for (let index = 0; index < this.bars.length; index++) {
const bar = this.bars[index];
const active = signalBarActive(this.rssi, index);
const active = signalBarActive(this.rssi, index, this.noSignalRssiValue);
const newFill = active ? activeBarsColorHex : this.inactiveBarsColorHex;
const newOpacity = active ? activeBarsOpacity : this.inactiveBarsOpacity;
if (newFill !== bar.fill() || newOpacity !== bar.opacity()) {

View File

@ -133,10 +133,10 @@ export const signalStrengthDefaultSettings: SignalStrengthWidgetSettings = {
padding: '12px'
};
export const signalBarActive = (rssi: number, index: number): boolean => {
export const signalBarActive = (rssi: number, index: number, minSignal: number): boolean => {
switch (index) {
case 0:
return rssi > -100;
return rssi > minSignal;
case 1:
return rssi >= -85;
case 2:

View File

@ -85,6 +85,10 @@ export const statusWidgetDefaultSettings: StatusWidgetSettings = {
getTimeSeries: {
key: 'state'
},
getAlarmStatus: {
severityList: null,
typeList: null
},
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,
@ -101,6 +105,10 @@ export const statusWidgetDefaultSettings: StatusWidgetSettings = {
getTimeSeries: {
key: 'state'
},
getAlarmStatus: {
severityList: null,
typeList: null
},
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,

View File

@ -104,6 +104,10 @@ export const powerButtonDefaultSettings: PowerButtonWidgetSettings = {
getTimeSeries: {
key: 'state'
},
getAlarmStatus: {
severityList: null,
typeList: null
},
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,
@ -120,6 +124,10 @@ export const powerButtonDefaultSettings: PowerButtonWidgetSettings = {
getTimeSeries: {
key: 'state'
},
getAlarmStatus: {
severityList: null,
typeList: null
},
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,

View File

@ -100,6 +100,10 @@ export const singleSwitchDefaultSettings: SingleSwitchWidgetSettings = {
getTimeSeries: {
key: 'state'
},
getAlarmStatus: {
severityList: null,
typeList: null
},
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,
@ -116,6 +120,10 @@ export const singleSwitchDefaultSettings: SingleSwitchWidgetSettings = {
getTimeSeries: {
key: 'state'
},
getAlarmStatus: {
severityList: null,
typeList: null
},
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,

View File

@ -101,6 +101,10 @@ export const sliderWidgetDefaultSettings: SliderWidgetSettings = {
getTimeSeries: {
key: 'state'
},
getAlarmStatus: {
severityList: null,
typeList: null
},
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,
@ -117,6 +121,10 @@ export const sliderWidgetDefaultSettings: SliderWidgetSettings = {
getTimeSeries: {
key: 'state'
},
getAlarmStatus: {
severityList: null,
typeList: null
},
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,

View File

@ -415,6 +415,10 @@ export const defaultGetValueSettings = (valueType: ValueType): GetValueSettings<
getTimeSeries: {
key: 'state'
},
getAlarmStatus: {
severityList: null,
typeList: null
},
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,

View File

@ -103,8 +103,31 @@
</div>
</ng-container>
</ng-template>
<ng-template [ngSwitchCase]="getValueAction.GET_ALARM_STATUS">
<ng-container formGroupName="getAlarmStatus">
<div class="tb-form-row space-between column-xs">
<div class="fixed-title-width" translate>alarm.alarm-severity</div>
<mat-chip-listbox multiple formControlName="severityList">
<mat-chip-option *ngFor="let alarmSeverity of alarmSeverities" [value]="alarmSeverity">
{{ alarmSeverityTranslationMap.get(alarmSeverity) | translate }}
</mat-chip-option>
</mat-chip-listbox>
</div>
<div class="tb-form-row column-xs">
<div class="fixed-title-width" translate>alarm.alarm-types</div>
<tb-entity-subtype-list subscriptSizing="dynamic"
formControlName="typeList"
appearance="outline"
class="flex-1"
[additionalClasses]="['tb-chips', 'flex']"
[entityType]="entityType.ALARM">
</tb-entity-subtype-list>
</div>
</ng-container>
</ng-template>
</ng-container>
<div *ngIf="getValueSettingsFormGroup.get('action').value !== getValueAction.DO_NOTHING"
<div *ngIf="getValueSettingsFormGroup.get('action').value !== getValueAction.DO_NOTHING &&
getValueSettingsFormGroup.get('action').value !== getValueAction.GET_ALARM_STATUS"
class="tb-form-panel stroked" formGroupName="dataToValue">
<div class="tb-form-row no-padding no-border column-xs">
<div class="fixed-title-width" translate>widgets.value-action.action-result-converter</div>

View File

@ -33,6 +33,8 @@ import { TargetDevice, widgetType } from '@shared/models/widget.models';
import { AttributeScope, DataKeyType, telemetryTypeTranslationsShort } from '@shared/models/telemetry/telemetry.models';
import { IAliasController } from '@core/api/widget-api.models';
import { WidgetService } from '@core/http/widget.service';
import { AlarmSeverity, alarmSeverityTranslations } from '@shared/models/alarm.models';
import { EntityType } from '@shared/models/entity-type.models';
@Component({
selector: 'tb-get-value-action-settings-panel',
@ -96,6 +98,9 @@ export class GetValueActionSettingsPanelComponent extends PageComponent implemen
getValueSettingsFormGroup: UntypedFormGroup;
alarmSeverities = Object.keys(AlarmSeverity) as AlarmSeverity[];
alarmSeverityTranslationMap = alarmSeverityTranslations;
constructor(private fb: UntypedFormBuilder,
private widgetService: WidgetService,
protected store: Store<AppState>) {
@ -122,6 +127,10 @@ export class GetValueActionSettingsPanelComponent extends PageComponent implemen
getTimeSeries: this.fb.group({
key: [this.getValueSettings?.getTimeSeries?.key, [Validators.required]]
}),
getAlarmStatus: this.fb.group({
severityList: [this.getValueSettings?.getAlarmStatus?.severityList],
typeList: [this.getValueSettings?.getAlarmStatus?.typeList]
}),
dataToValue: this.fb.group({
type: [this.getValueSettings?.dataToValue?.type, [Validators.required]],
dataToValueFunction: [this.getValueSettings?.dataToValue?.dataToValueFunction, [Validators.required]],
@ -159,6 +168,7 @@ export class GetValueActionSettingsPanelComponent extends PageComponent implemen
this.getValueSettingsFormGroup.get('executeRpc').disable({emitEvent: false});
this.getValueSettingsFormGroup.get('getAttribute').disable({emitEvent: false});
this.getValueSettingsFormGroup.get('getTimeSeries').disable({emitEvent: false});
this.getValueSettingsFormGroup.get('getAlarmStatus').disable({emitEvent: false});
switch (action) {
case GetValueAction.DO_NOTHING:
this.getValueSettingsFormGroup.get('defaultValue').enable({emitEvent: false});
@ -178,8 +188,11 @@ export class GetValueActionSettingsPanelComponent extends PageComponent implemen
case GetValueAction.GET_TIME_SERIES:
this.getValueSettingsFormGroup.get('getTimeSeries').enable({emitEvent: false});
break;
case GetValueAction.GET_ALARM_STATUS:
this.getValueSettingsFormGroup.get('getAlarmStatus').enable({emitEvent: false});
break;
}
if (action === GetValueAction.DO_NOTHING) {
if (action === GetValueAction.DO_NOTHING || action === GetValueAction.GET_ALARM_STATUS) {
this.getValueSettingsFormGroup.get('dataToValue').disable({emitEvent: false});
} else {
this.getValueSettingsFormGroup.get('dataToValue').enable({emitEvent: false});
@ -190,4 +203,6 @@ export class GetValueActionSettingsPanelComponent extends PageComponent implemen
}
}
}
protected readonly entityType = EntityType;
}

View File

@ -176,6 +176,9 @@ export class GetValueActionSettingsComponent implements OnInit, ControlValueAcce
case GetValueAction.GET_TIME_SERIES:
this.displayValue = this.translate.instant('widgets.value-action.get-time-series-text', {key: this.modelValue.getTimeSeries.key});
break;
case GetValueAction.GET_ALARM_STATUS:
this.displayValue = this.translate.instant('widgets.value-action.get-alarm-status-text');
break;
case GetValueAction.GET_DASHBOARD_STATE:
if (this.valueType === ValueType.BOOLEAN) {
const state = this.modelValue.dataToValue?.compareToValue;

View File

@ -16,7 +16,7 @@
import { Component, Injector } from '@angular/core';
import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { formatValue } from '@core/utils';
@ -88,7 +88,7 @@ export class SignalStrengthWidgetSettingsComponent extends WidgetSettingsCompone
background: [settings.background, []],
padding: [settings.padding, []],
noSignalRssiValue: [settings.noSignalRssiValue, []]
noSignalRssiValue: [settings.noSignalRssiValue, [Validators.max(-86)]]
});
}

View File

@ -16,12 +16,14 @@
import { AttributeScope } from '@shared/models/telemetry/telemetry.models';
import { widgetType } from '@shared/models/widget.models';
import { AlarmSeverity } from '@shared/models/alarm.models';
export enum GetValueAction {
DO_NOTHING = 'DO_NOTHING',
EXECUTE_RPC = 'EXECUTE_RPC',
GET_ATTRIBUTE = 'GET_ATTRIBUTE',
GET_TIME_SERIES = 'GET_TIME_SERIES',
GET_ALARM_STATUS = 'GET_ALARM_STATUS',
GET_DASHBOARD_STATE = 'GET_DASHBOARD_STATE'
}
@ -41,6 +43,7 @@ export const getValueActionTranslations = new Map<GetValueAction, string>(
[GetValueAction.EXECUTE_RPC, 'widgets.value-action.execute-rpc'],
[GetValueAction.GET_ATTRIBUTE, 'widgets.value-action.get-attribute'],
[GetValueAction.GET_TIME_SERIES, 'widgets.value-action.get-time-series'],
[GetValueAction.GET_ALARM_STATUS, 'widgets.value-action.get-alarm-status'],
[GetValueAction.GET_DASHBOARD_STATE, 'widgets.value-action.get-dashboard-state']
]
);
@ -52,6 +55,11 @@ export interface RpcSettings {
persistentPollingInterval: number;
}
export interface AlarmStatusSettings {
severityList: Array<AlarmSeverity>;
typeList: Array<string>;
}
export interface TelemetryValueSettings {
key: string;
}
@ -85,6 +93,7 @@ export interface GetValueSettings<V> extends ValueActionSettings {
executeRpc?: RpcSettings;
getAttribute: GetAttributeValueSettings;
getTimeSeries: TelemetryValueSettings;
getAlarmStatus: AlarmStatusSettings;
dataToValue: DataToValueSettings;
}

View File

@ -33,9 +33,9 @@ import {
TsValue
} from '@shared/models/query/query.models';
import { PageData } from '@shared/models/page/page-data';
import { alarmFields } from '@shared/models/alarm.models';
import { alarmFields, AlarmSeverity } from '@shared/models/alarm.models';
import { entityFields } from '@shared/models/entity.models';
import { isDefinedAndNotNull, isUndefined } from '@core/utils';
import { deepClone, isDefinedAndNotNull, isUndefined } from '@core/utils';
import { CmdWrapper, WsService, WsSubscriber } from '@shared/models/websocket/websocket.models';
import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service';
import { Notification, NotificationType } from '@shared/models/notification.models';
@ -141,6 +141,7 @@ export enum WsCmdType {
ENTITY_COUNT = 'ENTITY_COUNT',
ALARM_DATA = 'ALARM_DATA',
ALARM_COUNT = 'ALARM_COUNT',
ALARM_STATUS = 'ALARM_STATUS',
NOTIFICATIONS = 'NOTIFICATIONS',
NOTIFICATIONS_COUNT = 'NOTIFICATIONS_COUNT',
@ -149,6 +150,7 @@ export enum WsCmdType {
ALARM_DATA_UNSUBSCRIBE = 'ALARM_DATA_UNSUBSCRIBE',
ALARM_COUNT_UNSUBSCRIBE = 'ALARM_COUNT_UNSUBSCRIBE',
ALARM_STATUS_UNSUBSCRIBE = 'ALARM_STATUS_UNSUBSCRIBE',
ENTITY_DATA_UNSUBSCRIBE = 'ENTITY_DATA_UNSUBSCRIBE',
ENTITY_COUNT_UNSUBSCRIBE = 'ENTITY_COUNT_UNSUBSCRIBE',
NOTIFICATIONS_UNSUBSCRIBE = 'NOTIFICATIONS_UNSUBSCRIBE'
@ -298,6 +300,14 @@ export class AlarmCountCmd implements WebsocketCmd {
type = WsCmdType.ALARM_COUNT;
}
export class AlarmStatusCmd implements WebsocketCmd {
cmdId: number;
originatorId: EntityId;
severityList?: Array<AlarmSeverity>;
typeList?: Array<string>;
type = WsCmdType.ALARM_STATUS;
}
export class UnreadCountSubCmd implements WebsocketCmd {
cmdId: number;
type = WsCmdType.NOTIFICATIONS_COUNT;
@ -352,6 +362,11 @@ export class AlarmCountUnsubscribeCmd implements WebsocketCmd {
type = WsCmdType.ALARM_COUNT_UNSUBSCRIBE;
}
export class AlarmStatusUnsubscribeCmd implements WebsocketCmd {
cmdId: number;
type = WsCmdType.ALARM_STATUS_UNSUBSCRIBE;
}
export class UnsubscribeCmd implements WebsocketCmd {
cmdId: number;
type = WsCmdType.NOTIFICATIONS_UNSUBSCRIBE;
@ -432,6 +447,7 @@ export enum CmdUpdateType {
ENTITY_DATA = 'ENTITY_DATA',
ALARM_DATA = 'ALARM_DATA',
ALARM_COUNT_DATA = 'ALARM_COUNT_DATA',
ALARM_STATUS = 'ALARM_STATUS',
COUNT_DATA = 'COUNT_DATA',
NOTIFICATIONS_COUNT = 'NOTIFICATIONS_COUNT',
NOTIFICATIONS = 'NOTIFICATIONS'
@ -469,6 +485,11 @@ export interface AlarmCountUpdateMsg extends CmdUpdateMsg {
count: number;
}
export interface AlarmStatusUpdateMsg extends CmdUpdateMsg {
cmdUpdateType: CmdUpdateType.ALARM_STATUS;
active: boolean;
}
export interface NotificationCountUpdateMsg extends CmdUpdateMsg {
cmdUpdateType: CmdUpdateType.NOTIFICATIONS_COUNT;
totalUnreadCount: number;
@ -506,6 +527,11 @@ export const isAlarmCountUpdateMsg = (message: WebsocketDataMsg): message is Ala
return updateMsg.cmdId !== undefined && updateMsg.cmdUpdateType === CmdUpdateType.ALARM_COUNT_DATA;
};
export const isAlarmStatusUpdateMsg = (message: WebsocketDataMsg): message is AlarmCountUpdateMsg => {
const updateMsg = (message as CmdUpdateMsg);
return updateMsg.cmdId !== undefined && updateMsg.cmdUpdateType === CmdUpdateType.ALARM_STATUS;
};
export const isNotificationCountUpdateMsg = (message: WebsocketDataMsg): message is NotificationCountUpdateMsg => {
const updateMsg = (message as CmdUpdateMsg);
return updateMsg.cmdId !== undefined && updateMsg.cmdUpdateType === CmdUpdateType.NOTIFICATIONS_COUNT;
@ -705,6 +731,15 @@ export class AlarmCountUpdate extends CmdUpdate {
}
}
export class AlarmStatusUpdate extends CmdUpdate {
active: boolean;
constructor(msg: AlarmStatusUpdateMsg) {
super(msg);
this.active = msg.active;
}
}
export class NotificationCountUpdate extends CmdUpdate {
totalUnreadCount: number;
sequenceNumber: number;
@ -750,14 +785,29 @@ export class SharedTelemetrySubscriber {
return key;
}
private static createAlarmStatusSubscriberKey (entityId: EntityId, severityList: AlarmSeverity[] = null, typeList: string[] = null): string {
let key = entityId.entityType + '_' + entityId.id;
if (severityList) {
key += '_' + severityList.sort().join('_');
}
if (typeList) {
key += '_' + typeList.sort().join('_');
}
return key;
}
private subscribed = false;
private attributeDataSubject = connectable(this.sharedSubscriptionInfo.subscriber.attributeData$(),
{ connector: () => new ReplaySubject<Array<AttributeData>>(1)});
private alarmStatusSubject = connectable(this.sharedSubscriptionInfo.subscriber.alarmStatus$,
{ connector: () => new ReplaySubject<AlarmStatusUpdate>()});
private subscriptions = new Array<Subscription>();
public attributeData$: Observable<Array<AttributeData>> = this.attributeDataSubject; //this.attributeDataSubject.asObservable();
public alarmStatus$: Observable<AlarmStatusUpdate> = this.alarmStatusSubject;
public static createEntityAttributesSubscription(telemetryService: TelemetryWebsocketService,
entityId: EntityId, attributeScope: TelemetryType,
@ -781,6 +831,28 @@ export class SharedTelemetrySubscriber {
return sharedSubscriber;
}
public static createAlarmStatusSubscription(telemetryService: TelemetryWebsocketService,
entityId: EntityId, zone: NgZone, severityList: AlarmSeverity[] = null,
typeList: string[] = null): SharedTelemetrySubscriber {
const key = SharedTelemetrySubscriber.createAlarmStatusSubscriberKey(entityId, severityList, typeList);
let info = SharedTelemetrySubscriber.subscribersCache[key];
if (!info) {
const subscriber = TelemetrySubscriber.createAlarmStatusSubscription(
telemetryService, entityId, zone, severityList, typeList
);
info = {
key,
subscriber,
subscribed: false,
sharedSubscribers: new Set<SharedTelemetrySubscriber>()
};
SharedTelemetrySubscriber.subscribersCache[key] = info;
}
const sharedSubscriber = new SharedTelemetrySubscriber(info);
info.sharedSubscribers.add(sharedSubscriber);
return sharedSubscriber;
}
private constructor(private sharedSubscriptionInfo: SharedSubscriptionInfo) {
}
@ -788,6 +860,7 @@ export class SharedTelemetrySubscriber {
if (!this.subscribed) {
this.subscribed = true;
this.subscriptions.push(this.attributeDataSubject.connect());
this.subscriptions.push(this.alarmStatusSubject.connect());
if (!this.sharedSubscriptionInfo.subscribed) {
this.sharedSubscriptionInfo.subscriber.subscribe();
this.sharedSubscriptionInfo.subscribed = true;
@ -823,6 +896,7 @@ export class TelemetrySubscriber extends WsSubscriber {
private alarmDataSubject = new ReplaySubject<AlarmDataUpdate>(1);
private entityCountSubject = new ReplaySubject<EntityCountUpdate>(1);
private alarmCountSubject = new ReplaySubject<AlarmCountUpdate>(1);
private alarmStatusSubject = new ReplaySubject<AlarmStatusUpdate>(1);
private tsOffset = undefined;
public data$ = this.dataSubject.asObservable();
@ -830,6 +904,7 @@ export class TelemetrySubscriber extends WsSubscriber {
public alarmData$ = this.alarmDataSubject.asObservable();
public entityCount$ = this.entityCountSubject.asObservable();
public alarmCount$ = this.alarmCountSubject.asObservable();
public alarmStatus$ = this.alarmStatusSubject.asObservable();
public static createEntityAttributesSubscription(telemetryService: TelemetryWebsocketService,
entityId: EntityId, attributeScope: TelemetryType,
@ -851,6 +926,17 @@ export class TelemetrySubscriber extends WsSubscriber {
return subscriber;
}
public static createAlarmStatusSubscription(telemetryService: TelemetryWebsocketService, entityId: EntityId,
zone: NgZone, severityList: AlarmSeverity[] = null, typeList: string[] = null): TelemetrySubscriber {
const subscriptionCommand = new AlarmStatusCmd();
subscriptionCommand.originatorId = deepClone(entityId);
subscriptionCommand.severityList = severityList;
subscriptionCommand.typeList = typeList;
const subscriber = new TelemetrySubscriber(telemetryService, zone);
subscriber.subscriptionCommands.push(subscriptionCommand);
return subscriber;
}
public static createEntityFilterLatestSubscription(telemetryService: TelemetryWebsocketService,
entityFilter: EntityFilter, zone: NgZone,
latestKeys: EntityKey[] = null): TelemetrySubscriber {
@ -882,6 +968,7 @@ export class TelemetrySubscriber extends WsSubscriber {
this.alarmDataSubject.complete();
this.entityCountSubject.complete();
this.alarmCountSubject.complete();
this.alarmStatusSubject.complete();
super.complete();
}
@ -971,6 +1058,18 @@ export class TelemetrySubscriber extends WsSubscriber {
}
}
public onAlarmStatus(message: AlarmStatusUpdate) {
if (this.zone) {
this.zone.run(
() => {
this.alarmStatusSubject.next(message);
}
);
} else {
this.alarmStatusSubject.next(message);
}
}
public attributeData$(): Observable<Array<AttributeData>> {
const attributeData = new Array<AttributeData>();
return this.data$.pipe(

View File

@ -21,4 +21,4 @@ For example, we have a device that has the following IP address: 192.168.0.120:5
4. Allow any devices:
**Address filter:** *:*
**Address filter:** `*:*`

View File

@ -0,0 +1,11 @@
# Report Strategy for Data Transmission
This section allows you to configure the strategy for sending data. You can select one of the following behaviors:
1. **On Report Period** - Data is collected and sent at regular intervals, defined in milliseconds.
2. **On Value Change** - Data is transmitted immediately whenever a change is detected.
3. **On Value Change or Report Period** - Data is sent either when a change occurs or after a specified time period has elapsed since the last transmission.
4. **On Received** - Data is sent instantly upon being received.

View File

@ -596,6 +596,7 @@
"ack-time": "Acknowledged time",
"clear-time": "Cleared time",
"duration": "Duration",
"alarm-severity": "Alarm severity",
"alarm-severity-list": "Alarm severity list",
"any-severity": "Any severity",
"severity-critical": "Critical",
@ -634,6 +635,7 @@
"fetch-size": "Fetch size",
"fetch-size-required": "Fetch size is required.",
"fetch-size-error-min": "Minimum value is 10.",
"alarm-types": "Alarm types",
"alarm-type-list": "Alarm type list",
"any-type": "Any type",
"assigned-to-current-user": "Assigned to current user",
@ -7352,11 +7354,12 @@
"get-attribute": "Get attribute",
"set-attribute": "Set attribute",
"get-time-series": "Get time series",
"get-alarm-status": "Get alarm status",
"get-dashboard-state": "Get dashboard state",
"add-time-series": "Add time series",
"execute-rpc-text": "Execute RPC method '{{methodName}}'",
"get-attribute-text": "Use attribute '{{key}}'",
"get-time-series-text": "Use time series '{{key}}'",
"get-alarm-status-text": "Use alarm status",
"get-dashboard-state-text": "Use dashboard state",
"when-dashboard-state-is-text": "When dashboard state is '{{state}}'",
"when-dashboard-state-function-is-text": "When f(dashboard state) is '{{state}}'",
@ -7660,31 +7663,31 @@
"language": {
"language": "Language",
"locales": {
"ar_AE": "اَلْعَرَبِيَّةُ",
"ca_ES": "Catalan",
"cs_CZ": "Česky",
"da_DK": "Dansk",
"de_DE": "Deutsch",
"el_GR": "Ελληνικά",
"en_US": "English",
"es_ES": "Español",
"fa_IR": "فارسي",
"fr_FR": "Français",
"it_IT": "Italiano",
"ja_JP": "日本語",
"ka_GE": "ქართული",
"ko_KR": "한국어",
"lt_LT": "Lietuvių",
"lv_LV": "Latviešu",
"nl_BE": "Koninkrijk België",
"pl_PL": "Polski",
"pt_BR": "Português do Brasil",
"ro_RO": "Română",
"sl_SI": "Slovenščina",
"tr_TR": "Türkçe",
"uk_UA": "Українська",
"zh_CN": "简体中文",
"zh_TW": "繁體中文"
"ar_AE": "العربية (الإمارات العربية المتحدة)",
"ca_ES": "català (Espanya)",
"cs_CZ": "čeština (Česko)",
"da_DK": "dansk (Danmark)",
"de_DE": "Deutsch (Deutschland)",
"el_GR": "Ελληνικά (Ελλάδα)",
"en_US": "English (United States)",
"es_ES": "español (España)",
"fa_IR": "فارسی (ایران)",
"fr_FR": "français (France)",
"it_IT": "italiano (Italia)",
"ja_JP": "日本語 (日本)",
"ka_GE": "ქართული (საქართველო)",
"ko_KR": "한국어 (대한민국)",
"lt_LT": "lietuvių (Lietuva)",
"lv_LV": "latviešu (Latvija)",
"nl_BE": "Nederlands (België)",
"pl_PL": "polski (Polska)",
"pt_BR": "português (Brasil)",
"ro_RO": "română (România)",
"sl_SI": "slovenščina (Slovenija)",
"tr_TR": "Türkçe (Türkiye)",
"uk_UA": "українська (Україна)",
"zh_CN": "中文 (中国)",
"zh_TW": "中文 (台灣)"
}
}
}