diff --git a/application/src/main/data/json/system/scada_symbols/dynamic-horizontal-scale-hp.svg b/application/src/main/data/json/system/scada_symbols/dynamic-horizontal-scale-hp.svg index 17d2a21ed9..6a060c4e63 100644 --- a/application/src/main/data/json/system/scada_symbols/dynamic-horizontal-scale-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/dynamic-horizontal-scale-hp.svg @@ -1,4 +1,5 @@ -{ +<svg width="800" height="400" fill="none" version="1.1" viewBox="0 0 800 400" xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg"> + <tb:metadata><![CDATA[{ "title": "HP Dynamic horizontal scale", "description": "Dynamic horizontal scale with current volume value and dynamically configurable warning and critical scale.", "searchTags": [ @@ -83,7 +84,7 @@ }, { "tag": "target", - "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showTarget = ctx.properties.showTarget;\nvar targetValue = ctx.values.targetValue;\n\nif (showTarget && targetValue !== null) {\n element.show();\n var offset = calculateOffset(targetValue, minValue, maxValue);\n element.transform({translateX: -(Math.min(Math.max(offset-17, 0), 617))});\n} else {\n element.hide();\n}\n\n", + "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 640;\n\n return offset;\n}\n\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showTarget = ctx.properties.showTarget;\nvar targetValue = ctx.values.targetValue;\n\nif (showTarget && targetValue !== null) {\n element.show();\n var offset = calculateOffset(targetValue, minValue, maxValue);\n element.transform({translateX: -(640 - (Math.min(Math.max(offset+17, 36), 640)))});\n} else {\n element.hide();\n}\n\n", "actions": null }, { @@ -108,7 +109,7 @@ }, { "tag": "valuePosition", - "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar value = ctx.values.value;\n\nif (value !== null) {\n var offset = calculateOffset(value, minValue, maxValue);\n element.transform({translateX: -offset});\n}\n", + "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 638;\n\n return offset;\n}\n\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar value = ctx.values.value;\n\nif (value !== null) {\n var offset = calculateOffset(value, minValue, maxValue);\n element.transform({translateX: -(638 - offset)});\n}\n", "actions": null } ], @@ -547,7 +548,7 @@ "id": "units", "name": "{i18n:scada.symbol.units}", "type": "units", - "default": "°C", + "default": "&#176;C", "required": null, "subLabel": null, "divider": null, @@ -564,7 +565,7 @@ "name": "{i18n:scada.symbol.units}", "type": "font", "default": { - "size": 56, + "size": 40, "sizeUnit": "px", "family": "Roboto", "weight": "500", @@ -791,33 +792,39 @@ } ] } -Outdoor°C - 100 - 0 - - - 26 - + Outdoor + °C + + 100 + 0 + + + + 26 + + - - + + - - + + - - + + - - + + - - - - - \ No newline at end of file + + + + + + + diff --git a/application/src/main/data/json/system/scada_symbols/dynamic-vertical-scale-hp.svg b/application/src/main/data/json/system/scada_symbols/dynamic-vertical-scale-hp.svg index db5c5736a7..1310ee78b6 100644 --- a/application/src/main/data/json/system/scada_symbols/dynamic-vertical-scale-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/dynamic-vertical-scale-hp.svg @@ -1,4 +1,5 @@ -{ +<svg width="400" height="800" fill="none" version="1.1" viewBox="0 0 400 800" xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg"> + <tb:metadata><![CDATA[{ "title": "HP Dynamic vertical scale", "description": "Dynamic vertical scale with current volume value and dynamically configurable warning and critical scale.", "searchTags": [ @@ -547,7 +548,7 @@ "id": "units", "name": "{i18n:scada.symbol.units}", "type": "units", - "default": "°C", + "default": "&#176;C", "required": null, "subLabel": null, "divider": null, @@ -791,7 +792,9 @@ } ] } -Outdoor°C + Outdoor + °C + @@ -810,14 +813,18 @@ - - 100 - 0 - + + + 100 + 0 + + 26 - + + - - \ No newline at end of file + + + diff --git a/application/src/main/data/json/system/scada_symbols/filter-hp.svg b/application/src/main/data/json/system/scada_symbols/filter-hp.svg index c101b6f65d..3bec273373 100644 --- a/application/src/main/data/json/system/scada_symbols/filter-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/filter-hp.svg @@ -1,4 +1,5 @@ -{ +<svg width="200" height="400" fill="none" version="1.1" viewBox="0 0 200 400" xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg"> + <tb:metadata><![CDATA[{ "title": "HP Filter", "description": "Filter", "searchTags": [ @@ -258,15 +259,19 @@ } ] } - + + + + - + + - \ No newline at end of file + diff --git a/application/src/main/data/json/system/scada_symbols/heat-pump-hp.svg b/application/src/main/data/json/system/scada_symbols/heat-pump-hp.svg index b4959787dd..1c663c9b29 100644 --- a/application/src/main/data/json/system/scada_symbols/heat-pump-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/heat-pump-hp.svg @@ -1,5 +1,6 @@ -{ - "title": "Heat pump", +<svg width="800" height="600" fill="none" version="1.1" viewBox="0 0 800 600" xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg"> + <tb:metadata><![CDATA[{ + "title": "HP Heat pump", "description": "Heat pump", "searchTags": [ "pump" @@ -515,7 +516,7 @@ "id": "units", "name": "{i18n:scada.symbol.value-units}", "type": "units", - "default": "°C", + "default": "&#176;C", "required": null, "subLabel": null, "divider": null, @@ -545,7 +546,7 @@ } ] } - + @@ -571,27 +572,33 @@ - + + + - + + - + + - + + - + + - \ No newline at end of file + diff --git a/application/src/main/data/json/system/scada_symbols/horizontal-tank-hp.svg b/application/src/main/data/json/system/scada_symbols/horizontal-tank-hp.svg index 53b0f7cc8a..232159f2a5 100644 --- a/application/src/main/data/json/system/scada_symbols/horizontal-tank-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/horizontal-tank-hp.svg @@ -1,5 +1,6 @@ -{ - "title": "Horizontal tank", +<svg width="1e3" height="600" fill="none" version="1.1" viewBox="0 0 1e3 600" xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg"> + <tb:metadata><![CDATA[{ + "title": "HP Horizontal tank", "description": "Horizontal tank", "searchTags": [ "horizontal tank" @@ -32,12 +33,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))*570; \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))*592; \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 = 570 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(208, y, 240, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 198, 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(220, minorY, 240, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 3;\n var majorIntervalLength = 592 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(208, y, 240, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n if (i === 0) {\n majorTickText.attr({x: 198, y: y + 10, 'text-anchor': 'end', class: 'majorTickText'});\n } else if (i === majorIntervals) {\n majorTickText.attr({x: 198, y: y - 5, 'text-anchor': 'end', class: 'majorTickText'});\n } else {\n majorTickText.attr({x: 198, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n }\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(220, minorY, 240, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "actions": null }, { @@ -454,83 +455,90 @@ } ] } - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + - + + - \ No newline at end of file + diff --git a/application/src/main/data/json/system/scada_symbols/horizontal-valve-hp.svg b/application/src/main/data/json/system/scada_symbols/horizontal-valve-hp.svg index c8aa016020..c032a4b957 100644 --- a/application/src/main/data/json/system/scada_symbols/horizontal-valve-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/horizontal-valve-hp.svg @@ -1,4 +1,5 @@ -{ +<svg width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg"> + <tb:metadata><![CDATA[{ "title": "HP Horizontal valve", "description": "Horizontal valve with various states.", "searchTags": [ @@ -17,7 +18,7 @@ "stateRenderFunction": null, "actions": { "click": { - "actionFunction": "ctx.api.callAction(event, 'click');" + "actionFunction": "var opened = ctx.values.opened;\nvar action = opened ? 'close' : 'open';\n\nctx.api.callAction(event, action, undefined, {\n next: () => {\n ctx.api.setValue('opened', !opened);\n }\n});" } } }, @@ -76,6 +77,74 @@ "defaultSetValueSettings": null, "defaultWidgetActionSettings": null }, + { + "id": "open", + "name": "{i18n:scada.symbol.open}", + "hint": "{i18n:scada.symbol.open-hint}", + "group": null, + "type": "action", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": null, + "defaultSetValueSettings": { + "action": "SET_ATTRIBUTE", + "executeRpc": { + "method": "setState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "setAttribute": { + "scope": "SHARED_SCOPE", + "key": "open" + }, + "putTimeSeries": { + "key": "state" + }, + "valueToData": { + "type": "CONSTANT", + "constantValue": true, + "valueToDataFunction": "/* Convert input boolean value to RPC parameters or attribute/time-series value */\nreturn value;" + } + }, + "defaultWidgetActionSettings": null + }, + { + "id": "close", + "name": "{i18n:scada.symbol.close}", + "hint": "{i18n:scada.symbol.close-hint}", + "group": null, + "type": "action", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": null, + "defaultSetValueSettings": { + "action": "SET_ATTRIBUTE", + "executeRpc": { + "method": "setState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "setAttribute": { + "scope": "SHARED_SCOPE", + "key": "open" + }, + "putTimeSeries": { + "key": "state" + }, + "valueToData": { + "type": "CONSTANT", + "constantValue": false, + "valueToDataFunction": "/* Convert input boolean value to RPC parameters or attribute/time-series value */\nreturn value;" + } + }, + "defaultWidgetActionSettings": null + }, { "id": "warning", "name": "{i18n:scada.symbol.warning-state}", @@ -220,26 +289,6 @@ "setEntityId": false, "stateEntityParamName": null } - }, - { - "id": "click", - "name": "{i18n:scada.symbol.on-click}", - "hint": "{i18n:scada.symbol.on-click-hint}", - "group": null, - "type": "widgetAction", - "valueType": "BOOLEAN", - "trueLabel": null, - "falseLabel": null, - "stateLabel": null, - "defaultGetValueSettings": null, - "defaultSetValueSettings": null, - "defaultWidgetActionSettings": { - "type": "doNothing", - "targetDashboardStateId": null, - "openRightLayout": false, - "setEntityId": false, - "stateEntityParamName": null - } } ], "properties": [ @@ -309,20 +358,26 @@ } ] } - + + - + + + + + - + + - \ No newline at end of file + diff --git a/application/src/main/data/json/system/scada_symbols/long-horizontal-connector-hp.svg b/application/src/main/data/json/system/scada_symbols/long-horizontal-connector-hp.svg index 0e48099e89..4376bb2348 100644 --- a/application/src/main/data/json/system/scada_symbols/long-horizontal-connector-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/long-horizontal-connector-hp.svg @@ -1,5 +1,6 @@ -{ - "title": "Long horizontal connector", +<svg width="400" height="200" fill="none" version="1.1" viewBox="0 0 400 200" xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg"> + <tb:metadata><![CDATA[{ + "title": "HP Long horizontal connector", "description": "Long horizontal connector", "widgetSizeX": 2, "widgetSizeY": 1, @@ -170,5 +171,6 @@ } ] } - - \ No newline at end of file + + + diff --git a/application/src/main/data/json/system/scada_symbols/pool-hp.svg b/application/src/main/data/json/system/scada_symbols/pool-hp.svg index 83fb19b868..d6aed15dbf 100644 --- a/application/src/main/data/json/system/scada_symbols/pool-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/pool-hp.svg @@ -1,7 +1,6 @@ - { - "title": "Pool", + "title": "HP Pool", "description": "Pool", "searchTags": [ "pool" @@ -34,12 +33,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))*770; \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))*792; \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 = 20;\n var majorIntervalLength = 770 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(298, y, 330, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 288, 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(310, minorY, 330, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 3;\n var majorIntervalLength = 792 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(298, y, 330, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n if (i === 0) {\n majorTickText.attr({x: 288, y: y + 10, 'text-anchor': 'end', class: 'majorTickText'});\n } else if (i === majorIntervals) {\n majorTickText.attr({x: 288, y: y - 5, 'text-anchor': 'end', class: 'majorTickText'});\n } else {\n majorTickText.attr({x: 288, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n }\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(310, minorY, 330, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "actions": null }, { @@ -462,70 +461,70 @@ <path d="m2386 2c6.63 0 12 5.3726 12 12v772c0 6.627-5.37 12-12 12h-2372c-6.6274 0-12-5.373-12-12v-772c0-6.6274 5.3726-12 12-12h2372z" fill="#C8DFF7"/> </mask> <g mask="url(#mask0_3600_177361)"> - <rect transform="scale(1,-1)" x="2" y="-790" width="2396" height="615" fill="#c8dff7" style="stroke-width:3.1235" tb:tag="fluid-background"/> + <rect transform="scale(1,-1)" x="2" y="-798" width="2396" height="615" fill="#c8dff7" style="stroke-width:3.1235" tb:tag="fluid-background"/> </g> <g tb:tag="scale"> - <line x1="298" x2="330" y1="6.582" y2="6.582" stroke="#000" stroke-opacity=".38" stroke-width="3"/> - <line x1="310" x2="330" y1="22.319" y2="22.319" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="310" x2="330" y1="38.057" y2="38.057" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="310" x2="330" y1="53.793" y2="53.793" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="310" x2="330" y1="69.53" y2="69.53" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="298" x2="330" y1="85.268" y2="85.268" stroke="#000" stroke-opacity=".38" stroke-width="3"/> - <line x1="310" x2="330" y1="101" y2="101" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="310" x2="330" y1="116.74" y2="116.74" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="310" x2="330" y1="132.48" y2="132.48" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="310" x2="330" y1="148.22" y2="148.22" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="298" x2="330" y1="163.95" y2="163.95" stroke="#000" stroke-opacity=".38" stroke-width="3"/> - <line x1="310" x2="330" y1="179.69" y2="179.69" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="310" x2="330" y1="195.43" y2="195.43" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="310" x2="330" y1="211.16" y2="211.16" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="310" x2="330" y1="226.9" y2="226.9" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="298" x2="330" y1="242.64" y2="242.64" stroke="#000" stroke-opacity=".38" stroke-width="3"/> - <line x1="310" x2="330" y1="258.38" y2="258.38" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="310" x2="330" y1="274.11" y2="274.11" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="310" x2="330" y1="289.85" y2="289.85" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="310" x2="330" y1="305.59" y2="305.59" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="298" x2="330" y1="321.32" y2="321.32" stroke="#000" stroke-opacity=".38" stroke-width="3"/> - <line x1="310" x2="330" y1="337.06" y2="337.06" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="310" x2="330" y1="352.8" y2="352.8" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="310" x2="330" y1="368.53" y2="368.53" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="310" x2="330" y1="384.27" y2="384.27" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="298" x2="330" y1="400.01" y2="400.01" stroke="#000" stroke-opacity=".38" stroke-width="3"/> - <line x1="310" x2="330" y1="415.75" y2="415.75" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="310" x2="330" y1="431.48" y2="431.48" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="310" x2="330" y1="447.22" y2="447.22" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="310" x2="330" y1="462.96" y2="462.96" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="298" x2="330" y1="478.69" y2="478.69" stroke="#000" stroke-opacity=".38" stroke-width="3"/> - <line x1="310" x2="330" y1="494.43" y2="494.43" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="310" x2="330" y1="510.17" y2="510.17" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="310" x2="330" y1="525.9" y2="525.9" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="310" x2="330" y1="541.64" y2="541.64" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="298" x2="330" y1="557.38" y2="557.38" stroke="#000" stroke-opacity=".38" stroke-width="3"/> - <line x1="310" x2="330" y1="573.12" y2="573.12" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="310" x2="330" y1="588.85" y2="588.85" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="310" x2="330" y1="604.59" y2="604.59" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="310" x2="330" y1="620.33" y2="620.33" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="298" x2="330" y1="636.06" y2="636.06" stroke="#000" stroke-opacity=".38" stroke-width="3"/> - <line x1="310" x2="330" y1="651.8" y2="651.8" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="310" x2="330" y1="667.54" y2="667.54" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="310" x2="330" y1="683.28" y2="683.28" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="310" x2="330" y1="699.01" y2="699.01" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="298" x2="330" y1="714.75" y2="714.75" stroke="#000" stroke-opacity=".38" stroke-width="3"/> - <line x1="310" x2="330" y1="730.49" y2="730.49" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="310" x2="330" y1="746.22" y2="746.22" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="310" x2="330" y1="761.96" y2="761.96" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="310" x2="330" y1="777.7" y2="777.7" stroke="#000" stroke-opacity=".12" stroke-width="3"/> - <line x1="298" x2="330" y1="793.44" y2="793.44" stroke="#000" stroke-opacity=".38" stroke-width="3"/> - <path d="m257.58 2.8789v17.121h-2.824v-13.77l-4.184 1.418v-2.332l6.668-2.4375h0.34zm17.301 7.1602v2.789c0 1.336-0.133 2.4766-0.399 3.4219-0.257 0.9375-0.632 1.6992-1.125 2.2852-0.492 0.5859-1.082 1.0156-1.769 1.289-0.68 0.2735-1.441 0.4102-2.285 0.4102-0.672 0-1.297-0.086-1.875-0.2578-0.571-0.1719-1.086-0.4414-1.547-0.8086s-0.856-0.8399-1.184-1.418c-0.32-0.5859-0.57-1.2852-0.75-2.0977-0.172-0.8125-0.257-1.7539-0.257-2.8242v-2.789c0-1.3438 0.132-2.4766 0.398-3.3985 0.266-0.92968 0.644-1.6836 1.137-2.2617 0.492-0.58594 1.078-1.0117 1.757-1.2774 0.688-0.26562 1.454-0.39844 2.297-0.39844 0.68 0 1.305 0.08594 1.875 0.25782 0.578 0.16406 1.094 0.42578 1.547 0.78515 0.461 0.35938 0.852 0.82813 1.172 1.4062 0.328 0.57032 0.578 1.2617 0.75 2.0742 0.172 0.80469 0.258 1.7422 0.258 2.8125zm-2.824 3.1875v-3.6094c0-0.67969-0.039-1.2774-0.117-1.793-0.079-0.52344-0.196-0.96484-0.352-1.3242-0.148-0.36719-0.336-0.66406-0.563-0.89062-0.226-0.23438-0.484-0.40235-0.773-0.50391-0.289-0.10938-0.613-0.16406-0.973-0.16406-0.437 0-0.828 0.08593-1.172 0.25781-0.343 0.16406-0.632 0.42969-0.867 0.79687-0.234 0.36719-0.414 0.85157-0.539 1.4531-0.117 0.59375-0.176 1.3164-0.176 2.168v3.6094c0 0.6875 0.039 1.2929 0.118 1.8164 0.078 0.5234 0.195 0.9726 0.351 1.3476 0.156 0.3672 0.344 0.6719 0.563 0.9141 0.226 0.2344 0.484 0.4062 0.773 0.5156 0.297 0.1094 0.621 0.1641 0.973 0.1641 0.445 0 0.84-0.086 1.183-0.2578 0.344-0.1719 0.633-0.4454 0.868-0.8204 0.234-0.3828 0.41-0.8789 0.527-1.4882 0.117-0.6094 0.176-1.3399 0.176-2.1914zm16.715-3.1875v2.789c0 1.336-0.133 2.4766-0.399 3.4219-0.258 0.9375-0.633 1.6992-1.125 2.2852-0.492 0.5859-1.082 1.0156-1.769 1.289-0.68 0.2735-1.442 0.4102-2.286 0.4102-0.671 0-1.296-0.086-1.875-0.2578-0.57-0.1719-1.086-0.4414-1.546-0.8086-0.461-0.3672-0.856-0.8399-1.184-1.418-0.32-0.5859-0.57-1.2852-0.75-2.0977-0.172-0.8125-0.258-1.7539-0.258-2.8242v-2.789c0-1.3438 0.133-2.4766 0.399-3.3985 0.265-0.92968 0.644-1.6836 1.136-2.2617 0.492-0.58594 1.078-1.0117 1.758-1.2774 0.688-0.26562 1.453-0.39844 2.297-0.39844 0.68 0 1.305 0.08594 1.875 0.25782 0.578 0.16406 1.094 0.42578 1.547 0.78515 0.461 0.35938 0.851 0.82813 1.172 1.4062 0.328 0.57032 0.578 1.2617 0.75 2.0742 0.172 0.80469 0.258 1.7422 0.258 2.8125zm-2.825 3.1875v-3.6094c0-0.67969-0.039-1.2774-0.117-1.793-0.078-0.52344-0.195-0.96484-0.351-1.3242-0.149-0.36719-0.336-0.66406-0.563-0.89062-0.226-0.23438-0.484-0.40235-0.773-0.50391-0.289-0.10938-0.614-0.16406-0.973-0.16406-0.438 0-0.828 0.08593-1.172 0.25781-0.344 0.16406-0.633 0.42969-0.867 0.79687-0.234 0.36719-0.414 0.85157-0.539 1.4531-0.117 0.59375-0.176 1.3164-0.176 2.168v3.6094c0 0.6875 0.039 1.2929 0.117 1.8164 0.078 0.5234 0.196 0.9726 0.352 1.3476 0.156 0.3672 0.344 0.6719 0.562 0.9141 0.227 0.2344 0.485 0.4062 0.774 0.5156 0.297 0.1094 0.621 0.1641 0.972 0.1641 0.446 0 0.84-0.086 1.184-0.2578 0.344-0.1719 0.633-0.4454 0.867-0.8204 0.235-0.3828 0.41-0.8789 0.528-1.4882 0.117-0.6094 0.175-1.3399 0.175-2.1914z" fill="#000" fill-opacity=".38"/> - <path d="m266.03 95.474h0.235c1.078 0 1.976-0.1406 2.695-0.4218 0.727-0.2891 1.305-0.6875 1.734-1.1954 0.43-0.5078 0.739-1.1054 0.926-1.7929 0.188-0.6875 0.281-1.4336 0.281-2.2383v-2.9414c0-0.6953-0.074-1.3047-0.222-1.8281-0.141-0.5313-0.344-0.9727-0.61-1.3243-0.258-0.3593-0.558-0.6289-0.902-0.8085-0.336-0.1797-0.699-0.2696-1.09-0.2696-0.43 0-0.816 0.0977-1.16 0.293-0.336 0.1875-0.621 0.4492-0.856 0.7851-0.226 0.3282-0.402 0.7149-0.527 1.1602-0.117 0.4375-0.176 0.9063-0.176 1.4063 0 0.4687 0.055 0.9218 0.164 1.3593 0.118 0.4297 0.289 0.8125 0.516 1.1485 0.227 0.3359 0.512 0.6015 0.856 0.7968 0.343 0.1954 0.746 0.293 1.207 0.293 0.437 0 0.839-0.082 1.207-0.2461 0.367-0.1719 0.687-0.4023 0.961-0.6914 0.273-0.2891 0.488-0.6133 0.644-0.9726 0.156-0.3594 0.242-0.7266 0.258-1.1016l1.078 0.3281c0 0.5938-0.125 1.1797-0.375 1.7578-0.242 0.5703-0.582 1.0938-1.02 1.5703-0.429 0.4688-0.933 0.8438-1.511 1.125-0.571 0.2813-1.192 0.4219-1.864 0.4219-0.812 0-1.531-0.1523-2.156-0.457-0.617-0.3125-1.133-0.7344-1.547-1.2656-0.406-0.5313-0.711-1.1407-0.914-1.8282s-0.304-1.4101-0.304-2.1679c0-0.8203 0.125-1.5899 0.375-2.3086 0.25-0.7188 0.613-1.3516 1.089-1.8985 0.477-0.5547 1.055-0.9843 1.735-1.289 0.687-0.3125 1.465-0.4688 2.332-0.4688 0.922 0 1.73 0.1797 2.426 0.5391 0.695 0.3594 1.281 0.8555 1.757 1.4883 0.477 0.6328 0.836 1.3672 1.079 2.2031 0.242 0.8359 0.363 1.7344 0.363 2.6953v0.9961c0 1.0078-0.09 1.9805-0.27 2.918-0.179 0.9297-0.472 1.7929-0.879 2.5898-0.398 0.7891-0.925 1.4844-1.582 2.0859-0.648 0.5938-1.445 1.0586-2.39 1.3946-0.938 0.3281-2.039 0.4922-3.305 0.4922h-0.258v-2.3321zm22.739-7.7343v2.789c0 1.336-0.133 2.4766-0.399 3.4219-0.258 0.9375-0.633 1.6992-1.125 2.2852-0.492 0.5859-1.082 1.0156-1.769 1.289-0.68 0.2735-1.442 0.4102-2.286 0.4102-0.671 0-1.296-0.086-1.875-0.2578-0.57-0.1719-1.086-0.4414-1.546-0.8086-0.461-0.3672-0.856-0.8399-1.184-1.418-0.32-0.5859-0.57-1.2852-0.75-2.0977-0.172-0.8125-0.258-1.7539-0.258-2.8242v-2.789c0-1.3438 0.133-2.4766 0.399-3.3985 0.265-0.9297 0.644-1.6836 1.136-2.2617 0.492-0.5859 1.078-1.0117 1.758-1.2773 0.688-0.2657 1.453-0.3985 2.297-0.3985 0.68 0 1.305 0.086 1.875 0.2578 0.578 0.1641 1.094 0.4258 1.547 0.7852 0.461 0.3594 0.851 0.8281 1.172 1.4062 0.328 0.5704 0.578 1.2618 0.75 2.0743 0.172 0.8046 0.258 1.7421 0.258 2.8125zm-2.825 3.1875v-3.6094c0-0.6797-0.039-1.2774-0.117-1.793-0.078-0.5234-0.195-0.9648-0.351-1.3242-0.149-0.3672-0.336-0.6641-0.563-0.8906-0.226-0.2344-0.484-0.4024-0.773-0.5039-0.289-0.1094-0.614-0.1641-0.973-0.1641-0.438 0-0.828 0.0859-1.172 0.2578-0.344 0.1641-0.633 0.4297-0.867 0.7969s-0.414 0.8516-0.539 1.4531c-0.117 0.5938-0.176 1.3164-0.176 2.168v3.6094c0 0.6875 0.039 1.2929 0.117 1.8164 0.078 0.5234 0.196 0.9726 0.352 1.3476 0.156 0.3672 0.344 0.6719 0.562 0.9141 0.227 0.2344 0.485 0.4062 0.774 0.5156 0.297 0.1094 0.621 0.1641 0.972 0.1641 0.446 0 0.84-0.086 1.184-0.2578 0.344-0.1719 0.633-0.4454 0.867-0.8204 0.235-0.3828 0.41-0.8789 0.528-1.4882 0.117-0.6094 0.175-1.3399 0.175-2.1914z" fill="#000" fill-opacity=".38"/> + <line x1="298" x2="330" y1="3.5" y2="3.5" stroke="#000" stroke-opacity=".38" stroke-width="3"/> + <line x1="310" x2="330" y1="19.36" y2="19.36" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="310" x2="330" y1="35.22" y2="35.22" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="310" x2="330" y1="51.08" y2="51.08" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="310" x2="330" y1="66.94" y2="66.94" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="298" x2="330" y1="82.8" y2="82.8" stroke="#000" stroke-opacity=".38" stroke-width="3"/> + <line x1="310" x2="330" y1="98.66" y2="98.66" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="310" x2="330" y1="114.52" y2="114.52" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="310" x2="330" y1="130.38" y2="130.38" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="310" x2="330" y1="146.24" y2="146.24" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="298" x2="330" y1="162.1" y2="162.1" stroke="#000" stroke-opacity=".38" stroke-width="3"/> + <line x1="310" x2="330" y1="177.96" y2="177.96" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="310" x2="330" y1="193.82" y2="193.82" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="310" x2="330" y1="209.68" y2="209.68" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="310" x2="330" y1="225.54" y2="225.54" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="298" x2="330" y1="241.4" y2="241.4" stroke="#000" stroke-opacity=".38" stroke-width="3"/> + <line x1="310" x2="330" y1="257.26" y2="257.26" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="310" x2="330" y1="273.12" y2="273.12" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="310" x2="330" y1="288.98" y2="288.98" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="310" x2="330" y1="304.84" y2="304.84" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="298" x2="330" y1="320.7" y2="320.7" stroke="#000" stroke-opacity=".38" stroke-width="3"/> + <line x1="310" x2="330" y1="336.56" y2="336.56" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="310" x2="330" y1="352.42" y2="352.42" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="310" x2="330" y1="368.28" y2="368.28" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="310" x2="330" y1="384.14" y2="384.14" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="298" x2="330" y1="400" y2="400" stroke="#000" stroke-opacity=".38" stroke-width="3"/> + <line x1="310" x2="330" y1="415.86" y2="415.86" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="310" x2="330" y1="431.72" y2="431.72" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="310" x2="330" y1="447.58" y2="447.58" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="310" x2="330" y1="463.44" y2="463.44" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="298" x2="330" y1="479.3" y2="479.3" stroke="#000" stroke-opacity=".38" stroke-width="3"/> + <line x1="310" x2="330" y1="495.16" y2="495.16" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="310" x2="330" y1="511.02" y2="511.02" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="310" x2="330" y1="526.88" y2="526.88" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="310" x2="330" y1="542.74" y2="542.74" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="298" x2="330" y1="558.6" y2="558.6" stroke="#000" stroke-opacity=".38" stroke-width="3"/> + <line x1="310" x2="330" y1="574.46" y2="574.46" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="310" x2="330" y1="590.32" y2="590.32" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="310" x2="330" y1="606.18" y2="606.18" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="310" x2="330" y1="622.04" y2="622.04" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="298" x2="330" y1="637.9" y2="637.9" stroke="#000" stroke-opacity=".38" stroke-width="3"/> + <line x1="310" x2="330" y1="653.76" y2="653.76" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="310" x2="330" y1="669.62" y2="669.62" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="310" x2="330" y1="685.48" y2="685.48" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="310" x2="330" y1="701.34" y2="701.34" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="298" x2="330" y1="717.2" y2="717.2" stroke="#000" stroke-opacity=".38" stroke-width="3"/> + <line x1="310" x2="330" y1="733.06" y2="733.06" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="310" x2="330" y1="748.92" y2="748.92" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="310" x2="330" y1="764.78" y2="764.78" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="310" x2="330" y1="780.64" y2="780.64" stroke="#000" stroke-opacity=".12" stroke-width="3"/> + <line x1="298" x2="330" y1="796.5" y2="796.5" stroke="#000" stroke-opacity=".38" stroke-width="3"/> + <path d="m257.58 2.8789v17.121h-2.824v-13.77l-4.184 1.418v-2.332l6.668-2.4375zm17.301 7.1602v2.789c0 1.336-0.133 2.4766-0.399 3.4219-0.257 0.9375-0.632 1.6992-1.125 2.2852-0.492 0.5859-1.082 1.0156-1.769 1.289-0.68 0.2735-1.441 0.4102-2.285 0.4102-0.672 0-1.297-0.086-1.875-0.2578-0.571-0.1719-1.086-0.4414-1.547-0.8086s-0.856-0.8399-1.184-1.418c-0.32-0.5859-0.57-1.2852-0.75-2.0977-0.172-0.8125-0.257-1.7539-0.257-2.8242v-2.789c0-1.3438 0.132-2.4766 0.398-3.3985 0.266-0.92968 0.644-1.6836 1.137-2.2617 0.492-0.58594 1.078-1.0117 1.757-1.2774 0.688-0.26562 1.454-0.39844 2.297-0.39844 0.68 0 1.305 0.08594 1.875 0.25782 0.578 0.16406 1.094 0.42578 1.547 0.78515 0.461 0.35938 0.852 0.82813 1.172 1.4062 0.328 0.57032 0.578 1.2617 0.75 2.0742 0.172 0.80469 0.258 1.7422 0.258 2.8125zm-2.824 3.1875v-3.6094c0-0.67969-0.039-1.2774-0.117-1.793-0.079-0.52344-0.196-0.96484-0.352-1.3242-0.148-0.36719-0.336-0.66406-0.563-0.89062-0.226-0.23438-0.484-0.40235-0.773-0.50391-0.289-0.10938-0.613-0.16406-0.973-0.16406-0.437 0-0.828 0.08593-1.172 0.25781-0.343 0.16406-0.632 0.42969-0.867 0.79687-0.234 0.36719-0.414 0.85157-0.539 1.4531-0.117 0.59375-0.176 1.3164-0.176 2.168v3.6094c0 0.6875 0.039 1.2929 0.118 1.8164 0.078 0.5234 0.195 0.9726 0.351 1.3476 0.156 0.3672 0.344 0.6719 0.563 0.9141 0.226 0.2344 0.484 0.4062 0.773 0.5156 0.297 0.1094 0.621 0.1641 0.973 0.1641 0.445 0 0.84-0.086 1.183-0.2578 0.344-0.1719 0.633-0.4454 0.868-0.8204 0.234-0.3828 0.41-0.8789 0.527-1.4882 0.117-0.6094 0.176-1.3399 0.176-2.1914zm16.715-3.1875v2.789c0 1.336-0.133 2.4766-0.399 3.4219-0.258 0.9375-0.633 1.6992-1.125 2.2852-0.492 0.5859-1.082 1.0156-1.769 1.289-0.68 0.2735-1.442 0.4102-2.286 0.4102-0.671 0-1.296-0.086-1.875-0.2578-0.57-0.1719-1.086-0.4414-1.546-0.8086-0.461-0.3672-0.856-0.8399-1.184-1.418-0.32-0.5859-0.57-1.2852-0.75-2.0977-0.172-0.8125-0.258-1.7539-0.258-2.8242v-2.789c0-1.3438 0.133-2.4766 0.399-3.3985 0.265-0.92968 0.644-1.6836 1.136-2.2617 0.492-0.58594 1.078-1.0117 1.758-1.2774 0.688-0.26562 1.453-0.39844 2.297-0.39844 0.68 0 1.305 0.08594 1.875 0.25782 0.578 0.16406 1.094 0.42578 1.547 0.78515 0.461 0.35938 0.851 0.82813 1.172 1.4062 0.328 0.57032 0.578 1.2617 0.75 2.0742 0.172 0.80469 0.258 1.7422 0.258 2.8125zm-2.825 3.1875v-3.6094c0-0.67969-0.039-1.2774-0.117-1.793-0.078-0.52344-0.195-0.96484-0.351-1.3242-0.149-0.36719-0.336-0.66406-0.563-0.89062-0.226-0.23438-0.484-0.40235-0.773-0.50391-0.289-0.10938-0.614-0.16406-0.973-0.16406-0.438 0-0.828 0.08593-1.172 0.25781-0.344 0.16406-0.633 0.42969-0.867 0.79687-0.234 0.36719-0.414 0.85157-0.539 1.4531-0.117 0.59375-0.176 1.3164-0.176 2.168v3.6094c0 0.6875 0.039 1.2929 0.117 1.8164 0.078 0.5234 0.196 0.9726 0.352 1.3476 0.156 0.3672 0.344 0.6719 0.562 0.9141 0.227 0.2344 0.485 0.4062 0.774 0.5156 0.297 0.1094 0.621 0.1641 0.972 0.1641 0.446 0 0.84-0.086 1.184-0.2578 0.344-0.1719 0.633-0.4454 0.867-0.8204 0.235-0.3828 0.41-0.8789 0.528-1.4882 0.117-0.6094 0.175-1.3399 0.175-2.1914z" fill="#000" fill-opacity=".38"/> + <path d="m266.03 95.474h0.235c1.078 0 1.976-0.1406 2.695-0.4218 0.727-0.2891 1.305-0.6875 1.734-1.1954 0.43-0.5078 0.739-1.1054 0.926-1.7929 0.188-0.6875 0.281-1.4336 0.281-2.2383v-2.9414c0-0.6953-0.074-1.3047-0.222-1.8281-0.141-0.5313-0.344-0.9727-0.61-1.3243-0.258-0.3593-0.558-0.6289-0.902-0.8085-0.336-0.1797-0.699-0.2696-1.09-0.2696-0.43 0-0.816 0.0977-1.16 0.293-0.336 0.1875-0.621 0.4492-0.856 0.7851-0.226 0.3282-0.402 0.7149-0.527 1.1602-0.117 0.4375-0.176 0.9063-0.176 1.4063 0 0.4687 0.055 0.9218 0.164 1.3593 0.118 0.4297 0.289 0.8125 0.516 1.1485 0.227 0.3359 0.512 0.6015 0.856 0.7968 0.343 0.1954 0.746 0.293 1.207 0.293 0.437 0 0.839-0.082 1.207-0.2461 0.367-0.1719 0.687-0.4023 0.961-0.6914 0.273-0.2891 0.488-0.6133 0.644-0.9726 0.156-0.3594 0.242-0.7266 0.258-1.1016l1.078 0.3281c0 0.5938-0.125 1.1797-0.375 1.7578-0.242 0.5703-0.582 1.0938-1.02 1.5703-0.429 0.4688-0.933 0.8438-1.511 1.125-0.571 0.2813-1.192 0.4219-1.864 0.4219-0.812 0-1.531-0.1523-2.156-0.457-0.617-0.3125-1.133-0.7344-1.547-1.2656-0.406-0.5313-0.711-1.1407-0.914-1.8282s-0.304-1.4101-0.304-2.1679c0-0.8203 0.125-1.5899 0.375-2.3086 0.25-0.7188 0.613-1.3516 1.089-1.8985 0.477-0.5547 1.055-0.9843 1.735-1.289 0.687-0.3125 1.465-0.4688 2.332-0.4688 0.922 0 1.73 0.1797 2.426 0.5391 0.695 0.3594 1.281 0.8555 1.757 1.4883 0.477 0.6328 0.836 1.3672 1.079 2.2031 0.242 0.8359 0.363 1.7344 0.363 2.6953v0.9961c0 1.0078-0.09 1.9805-0.27 2.918-0.179 0.9297-0.472 1.7929-0.879 2.5898-0.398 0.7891-0.925 1.4844-1.582 2.0859-0.648 0.5938-1.445 1.0586-2.39 1.3946-0.938 0.3281-2.039 0.4922-3.305 0.4922h-0.258zm22.739-7.7343v2.789c0 1.336-0.133 2.4766-0.399 3.4219-0.258 0.9375-0.633 1.6992-1.125 2.2852-0.492 0.5859-1.082 1.0156-1.769 1.289-0.68 0.2735-1.442 0.4102-2.286 0.4102-0.671 0-1.296-0.086-1.875-0.2578-0.57-0.1719-1.086-0.4414-1.546-0.8086-0.461-0.3672-0.856-0.8399-1.184-1.418-0.32-0.5859-0.57-1.2852-0.75-2.0977-0.172-0.8125-0.258-1.7539-0.258-2.8242v-2.789c0-1.3438 0.133-2.4766 0.399-3.3985 0.265-0.9297 0.644-1.6836 1.136-2.2617 0.492-0.5859 1.078-1.0117 1.758-1.2773 0.688-0.2657 1.453-0.3985 2.297-0.3985 0.68 0 1.305 0.086 1.875 0.2578 0.578 0.1641 1.094 0.4258 1.547 0.7852 0.461 0.3594 0.851 0.8281 1.172 1.4062 0.328 0.5704 0.578 1.2618 0.75 2.0743 0.172 0.8046 0.258 1.7421 0.258 2.8125zm-2.825 3.1875v-3.6094c0-0.6797-0.039-1.2774-0.117-1.793-0.078-0.5234-0.195-0.9648-0.351-1.3242-0.149-0.3672-0.336-0.6641-0.563-0.8906-0.226-0.2344-0.484-0.4024-0.773-0.5039-0.289-0.1094-0.614-0.1641-0.973-0.1641-0.438 0-0.828 0.0859-1.172 0.2578-0.344 0.1641-0.633 0.4297-0.867 0.7969s-0.414 0.8516-0.539 1.4531c-0.117 0.5938-0.176 1.3164-0.176 2.168v3.6094c0 0.6875 0.039 1.2929 0.117 1.8164 0.078 0.5234 0.196 0.9726 0.352 1.3476 0.156 0.3672 0.344 0.6719 0.562 0.9141 0.227 0.2344 0.485 0.4062 0.774 0.5156 0.297 0.1094 0.621 0.1641 0.972 0.1641 0.446 0 0.84-0.086 1.184-0.2578 0.344-0.1719 0.633-0.4454 0.867-0.8204 0.235-0.3828 0.41-0.8789 0.528-1.4882 0.117-0.6094 0.175-1.3399 0.175-2.1914z" fill="#000" fill-opacity=".38"/> <path d="m274.91 170.75c0 1.063-0.246 1.957-0.738 2.684-0.492 0.726-1.164 1.277-2.016 1.652-0.844 0.367-1.797 0.551-2.859 0.551-1.063 0-2.02-0.184-2.871-0.551-0.852-0.375-1.524-0.926-2.016-1.652-0.492-0.727-0.738-1.621-0.738-2.684 0-0.703 0.136-1.34 0.41-1.91 0.273-0.578 0.66-1.074 1.16-1.488 0.508-0.422 1.102-0.746 1.781-0.973 0.688-0.227 1.438-0.34 2.25-0.34 1.078 0 2.043 0.199 2.895 0.598 0.851 0.398 1.519 0.949 2.004 1.652 0.492 0.703 0.738 1.524 0.738 2.461zm-2.836-0.141c0-0.57-0.117-1.07-0.351-1.5-0.235-0.429-0.563-0.761-0.985-0.996-0.422-0.234-0.91-0.351-1.465-0.351-0.562 0-1.05 0.117-1.465 0.351-0.414 0.235-0.738 0.567-0.972 0.996-0.227 0.43-0.34 0.93-0.34 1.5 0 0.579 0.113 1.079 0.34 1.5 0.226 0.414 0.551 0.731 0.972 0.95 0.422 0.218 0.918 0.328 1.489 0.328 0.57 0 1.062-0.11 1.476-0.328 0.414-0.219 0.735-0.536 0.961-0.95 0.227-0.421 0.34-0.921 0.34-1.5zm2.449-7.781c0 0.852-0.226 1.61-0.679 2.274-0.446 0.664-1.063 1.187-1.852 1.57-0.789 0.375-1.687 0.562-2.695 0.562-1.016 0-1.922-0.187-2.719-0.562-0.789-0.383-1.41-0.906-1.863-1.57-0.446-0.664-0.668-1.422-0.668-2.274 0-1.015 0.222-1.871 0.668-2.566 0.453-0.703 1.074-1.239 1.863-1.606s1.691-0.55 2.707-0.55 1.918 0.183 2.707 0.55 1.406 0.903 1.852 1.606c0.453 0.695 0.679 1.551 0.679 2.566zm-2.824 0.094c0-0.508-0.101-0.953-0.305-1.336-0.195-0.391-0.472-0.695-0.832-0.914-0.359-0.219-0.785-0.328-1.277-0.328s-0.918 0.105-1.277 0.316c-0.36 0.211-0.637 0.508-0.832 0.891-0.196 0.383-0.293 0.84-0.293 1.371 0 0.523 0.097 0.98 0.293 1.371 0.195 0.383 0.472 0.684 0.832 0.902 0.367 0.219 0.797 0.328 1.289 0.328s0.918-0.109 1.277-0.328c0.36-0.218 0.637-0.519 0.832-0.902 0.195-0.391 0.293-0.848 0.293-1.371zm17.067 2.519v2.79c0 1.335-0.133 2.476-0.399 3.421-0.258 0.938-0.633 1.7-1.125 2.286-0.492 0.585-1.082 1.015-1.769 1.289-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.809-0.461-0.367-0.856-0.839-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.824v-2.79c0-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.011 1.758-1.277 0.688-0.266 1.453-0.398 2.297-0.398 0.68 0 1.305 0.085 1.875 0.257 0.578 0.164 1.094 0.426 1.547 0.785 0.461 0.36 0.851 0.829 1.172 1.407 0.328 0.57 0.578 1.261 0.75 2.074 0.172 0.805 0.258 1.742 0.258 2.812zm-2.825 3.188v-3.609c0-0.68-0.039-1.278-0.117-1.793-0.078-0.524-0.195-0.965-0.351-1.325-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.258-0.344 0.164-0.633 0.429-0.867 0.796-0.234 0.368-0.414 0.852-0.539 1.454-0.117 0.593-0.176 1.316-0.176 2.168v3.609c0 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.445 0.867-0.82c0.235-0.383 0.41-0.879 0.528-1.489 0.117-0.609 0.175-1.339 0.175-2.191z" fill="#000" fill-opacity=".38"/> - <path d="m275.09 236.04v1.547l-6.82 15.516h-2.977l6.809-14.813h-8.836v-2.25h11.824zm13.68 7.102v2.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="m275.09 236.04v1.547l-6.82 15.516h-2.977l6.809-14.813h-8.836v-2.25zm13.68 7.102v2.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="m272.29 313.63h0.352v2.308h-0.2c-1.007 0-1.879 0.157-2.613 0.469-0.726 0.313-1.324 0.742-1.793 1.289s-0.82 1.188-1.055 1.922c-0.226 0.727-0.339 1.5-0.339 2.32v2.684c0 0.68 0.074 1.281 0.222 1.805 0.149 0.515 0.356 0.949 0.621 1.3 0.274 0.344 0.586 0.606 0.938 0.786 0.351 0.179 0.73 0.269 1.137 0.269 0.421 0 0.804-0.086 1.148-0.258 0.344-0.179 0.637-0.425 0.879-0.738 0.242-0.312 0.426-0.684 0.551-1.113 0.125-0.43 0.187-0.899 0.187-1.406 0-0.485-0.062-0.938-0.187-1.36-0.117-0.43-0.293-0.805-0.528-1.125-0.234-0.328-0.527-0.582-0.879-0.762-0.343-0.187-0.742-0.281-1.195-0.281-0.562 0-1.074 0.133-1.535 0.399-0.453 0.265-0.82 0.613-1.102 1.043-0.273 0.421-0.421 0.871-0.445 1.347l-1.078-0.351c0.063-0.727 0.223-1.379 0.48-1.957 0.266-0.578 0.614-1.071 1.043-1.477 0.43-0.406 0.926-0.715 1.489-0.926 0.57-0.218 1.195-0.328 1.875-0.328 0.828 0 1.547 0.156 2.156 0.469 0.609 0.312 1.113 0.738 1.512 1.277 0.406 0.532 0.707 1.141 0.902 1.828 0.203 0.68 0.305 1.391 0.305 2.133 0 0.821-0.125 1.586-0.375 2.297-0.25 0.703-0.617 1.32-1.102 1.852-0.476 0.531-1.058 0.945-1.746 1.242-0.68 0.297-1.453 0.445-2.32 0.445-0.914 0-1.731-0.176-2.449-0.527-0.711-0.352-1.317-0.836-1.817-1.453-0.492-0.617-0.867-1.328-1.125-2.133s-0.387-1.656-0.387-2.555v-1.172c0-1.297 0.164-2.519 0.493-3.668 0.328-1.156 0.832-2.175 1.511-3.058 0.688-0.883 1.567-1.574 2.637-2.074 1.07-0.508 2.348-0.762 3.832-0.762zm16.481 7.207v2.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.016-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.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.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.426 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.743 0.258 2.813zm-2.825 3.187v-3.609c0-0.68-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.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.368 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="m266.72 400.57-2.25-0.55 0.925-8.578h9.2v2.39h-6.856l-0.469 4.184c0.266-0.157 0.633-0.309 1.102-0.457 0.469-0.157 1.004-0.235 1.605-0.235 0.805 0 1.524 0.133 2.157 0.399 0.64 0.258 1.183 0.636 1.629 1.136 0.445 0.493 0.785 1.094 1.019 1.805 0.235 0.703 0.352 1.496 0.352 2.379 0 0.789-0.117 1.527-0.352 2.215-0.226 0.687-0.57 1.293-1.031 1.816-0.461 0.524-1.043 0.934-1.746 1.231-0.696 0.289-1.52 0.433-2.473 0.433-0.711 0-1.394-0.101-2.051-0.304-0.648-0.211-1.23-0.52-1.746-0.926-0.515-0.414-0.929-0.922-1.242-1.524-0.312-0.609-0.492-1.308-0.539-2.097h2.766c0.07 0.555 0.222 1.027 0.457 1.418 0.242 0.383 0.562 0.676 0.961 0.879 0.398 0.203 0.859 0.304 1.382 0.304 0.477 0 0.887-0.082 1.231-0.246 0.344-0.172 0.629-0.414 0.855-0.726 0.235-0.321 0.407-0.696 0.516-1.125 0.117-0.43 0.176-0.907 0.176-1.43 0-0.5-0.067-0.957-0.199-1.371-0.125-0.414-0.317-0.774-0.575-1.078-0.25-0.305-0.566-0.539-0.949-0.703-0.383-0.172-0.824-0.258-1.324-0.258-0.672 0-1.188 0.098-1.547 0.293-0.352 0.195-0.68 0.437-0.984 0.726zm22.047-2.027v2.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"/> - <path d="m275.51 480.12v2.25h-12.305l-0.094-1.699 7.36-11.531h2.261l-2.449 4.195-4.23 6.785h9.457zm-2.133-10.98v17.062h-2.824v-17.062h2.824zm15.391 7.101v2.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="m267.07 554.09h1.688c0.656 0 1.199-0.113 1.629-0.34 0.437-0.227 0.761-0.539 0.972-0.938 0.211-0.398 0.317-0.855 0.317-1.371 0-0.539-0.098-1-0.293-1.382-0.188-0.391-0.477-0.692-0.867-0.903-0.383-0.211-0.872-0.316-1.465-0.316-0.5 0-0.953 0.101-1.36 0.304-0.398 0.196-0.715 0.477-0.949 0.844-0.234 0.36-0.351 0.789-0.351 1.289h-2.836c0-0.906 0.238-1.711 0.714-2.414 0.477-0.703 1.125-1.254 1.946-1.652 0.828-0.406 1.758-0.609 2.789-0.609 1.101 0 2.062 0.183 2.883 0.55 0.828 0.36 1.472 0.899 1.933 1.617 0.461 0.719 0.692 1.61 0.692 2.672 0 0.485-0.114 0.977-0.34 1.477-0.227 0.5-0.563 0.957-1.008 1.371-0.445 0.406-1 0.738-1.664 0.996-0.664 0.25-1.434 0.375-2.309 0.375h-2.121v-1.57zm0 2.203v-1.547h2.121c1 0 1.852 0.117 2.555 0.352 0.711 0.234 1.289 0.558 1.734 0.972 0.446 0.407 0.77 0.871 0.973 1.395 0.211 0.523 0.316 1.078 0.316 1.664 0 0.797-0.144 1.508-0.433 2.133-0.281 0.617-0.684 1.14-1.207 1.57-0.524 0.43-1.137 0.754-1.84 0.973-0.695 0.218-1.453 0.328-2.273 0.328-0.735 0-1.438-0.102-2.11-0.305s-1.273-0.504-1.805-0.902c-0.531-0.407-0.953-0.91-1.265-1.512-0.305-0.609-0.457-1.312-0.457-2.109h2.824c0 0.507 0.117 0.957 0.352 1.347 0.242 0.383 0.578 0.684 1.007 0.903 0.438 0.218 0.938 0.328 1.5 0.328 0.594 0 1.106-0.106 1.536-0.317 0.429-0.211 0.757-0.523 0.984-0.937 0.234-0.414 0.352-0.914 0.352-1.5 0-0.664-0.129-1.203-0.387-1.617s-0.625-0.719-1.102-0.914c-0.476-0.204-1.039-0.305-1.687-0.305h-1.688zm21.696-2.356v2.79c0 1.335-0.133 2.476-0.399 3.421-0.258 0.938-0.633 1.7-1.125 2.286-0.492 0.585-1.082 1.015-1.769 1.289-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.809-0.461-0.367-0.856-0.839-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.824v-2.79c0-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.011 1.758-1.277 0.688-0.266 1.453-0.398 2.297-0.398 0.68 0 1.305 0.085 1.875 0.257 0.578 0.164 1.094 0.426 1.547 0.785 0.461 0.36 0.851 0.829 1.172 1.407 0.328 0.57 0.578 1.261 0.75 2.074 0.172 0.805 0.258 1.742 0.258 2.812zm-2.825 3.188v-3.609c0-0.68-0.039-1.278-0.117-1.793-0.078-0.524-0.195-0.965-0.351-1.325-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.258-0.344 0.164-0.633 0.429-0.867 0.796-0.234 0.368-0.414 0.852-0.539 1.454-0.117 0.593-0.176 1.316-0.176 2.168v3.609c0 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.445 0.867-0.82c0.235-0.383 0.41-0.879 0.528-1.489 0.117-0.609 0.175-1.339 0.175-2.191z" fill="#000" fill-opacity=".38"/> - <path d="m275.2 639.35v2.25h-11.437v-1.934l5.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.347 0.149-0.407 0.223-0.801 0.223-1.184 0-0.539-0.102-1.012-0.305-1.418-0.195-0.414-0.484-0.738-0.867-0.973-0.383-0.242-0.848-0.363-1.395-0.363-0.632 0-1.164 0.137-1.593 0.41-0.43 0.274-0.754 0.653-0.973 1.137-0.219 0.476-0.328 1.023-0.328 1.641h-2.824c0-0.993 0.226-1.899 0.679-2.719 0.453-0.828 1.11-1.485 1.969-1.969 0.86-0.492 1.895-0.738 3.106-0.738 1.14 0 2.109 0.191 2.906 0.574s1.402 0.926 1.816 1.629c0.422 0.703 0.633 1.535 0.633 2.496 0 0.531-0.086 1.059-0.258 1.582s-0.418 1.047-0.738 1.57c-0.313 0.516-0.684 1.035-1.113 1.559-0.43 0.516-0.903 1.039-1.418 1.57l-3.692 4.067h7.875zm13.575-7.711v2.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="m271.47 702.18v17.121h-2.824v-13.77l-4.184 1.418v-2.332l6.668-2.437h0.34zm17.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.016-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.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.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.426 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.743 0.258 2.813zm-2.825 3.187v-3.609c0-0.68-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.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.368 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="m275.51 480.12v2.25h-12.305l-0.094-1.699 7.36-11.531h2.261l-2.449 4.195-4.23 6.785zm-2.133-10.98v17.062h-2.824v-17.062zm15.391 7.101v2.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="m267.07 554.09h1.688c0.656 0 1.199-0.113 1.629-0.34 0.437-0.227 0.761-0.539 0.972-0.938 0.211-0.398 0.317-0.855 0.317-1.371 0-0.539-0.098-1-0.293-1.382-0.188-0.391-0.477-0.692-0.867-0.903-0.383-0.211-0.872-0.316-1.465-0.316-0.5 0-0.953 0.101-1.36 0.304-0.398 0.196-0.715 0.477-0.949 0.844-0.234 0.36-0.351 0.789-0.351 1.289h-2.836c0-0.906 0.238-1.711 0.714-2.414 0.477-0.703 1.125-1.254 1.946-1.652 0.828-0.406 1.758-0.609 2.789-0.609 1.101 0 2.062 0.183 2.883 0.55 0.828 0.36 1.472 0.899 1.933 1.617 0.461 0.719 0.692 1.61 0.692 2.672 0 0.485-0.114 0.977-0.34 1.477-0.227 0.5-0.563 0.957-1.008 1.371-0.445 0.406-1 0.738-1.664 0.996-0.664 0.25-1.434 0.375-2.309 0.375h-2.121zm0 2.203v-1.547h2.121c1 0 1.852 0.117 2.555 0.352 0.711 0.234 1.289 0.558 1.734 0.972 0.446 0.407 0.77 0.871 0.973 1.395 0.211 0.523 0.316 1.078 0.316 1.664 0 0.797-0.144 1.508-0.433 2.133-0.281 0.617-0.684 1.14-1.207 1.57-0.524 0.43-1.137 0.754-1.84 0.973-0.695 0.218-1.453 0.328-2.273 0.328-0.735 0-1.438-0.102-2.11-0.305s-1.273-0.504-1.805-0.902c-0.531-0.407-0.953-0.91-1.265-1.512-0.305-0.609-0.457-1.312-0.457-2.109h2.824c0 0.507 0.117 0.957 0.352 1.347 0.242 0.383 0.578 0.684 1.007 0.903 0.438 0.218 0.938 0.328 1.5 0.328 0.594 0 1.106-0.106 1.536-0.317 0.429-0.211 0.757-0.523 0.984-0.937 0.234-0.414 0.352-0.914 0.352-1.5 0-0.664-0.129-1.203-0.387-1.617s-0.625-0.719-1.102-0.914c-0.476-0.204-1.039-0.305-1.687-0.305zm21.696-2.356v2.79c0 1.335-0.133 2.476-0.399 3.421-0.258 0.938-0.633 1.7-1.125 2.286-0.492 0.585-1.082 1.015-1.769 1.289-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.809-0.461-0.367-0.856-0.839-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.824v-2.79c0-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.011 1.758-1.277 0.688-0.266 1.453-0.398 2.297-0.398 0.68 0 1.305 0.085 1.875 0.257 0.578 0.164 1.094 0.426 1.547 0.785 0.461 0.36 0.851 0.829 1.172 1.407 0.328 0.57 0.578 1.261 0.75 2.074 0.172 0.805 0.258 1.742 0.258 2.812zm-2.825 3.188v-3.609c0-0.68-0.039-1.278-0.117-1.793-0.078-0.524-0.195-0.965-0.351-1.325-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.258-0.344 0.164-0.633 0.429-0.867 0.796-0.234 0.368-0.414 0.852-0.539 1.454-0.117 0.593-0.176 1.316-0.176 2.168v3.609c0 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.445 0.867-0.82c0.235-0.383 0.41-0.879 0.528-1.489 0.117-0.609 0.175-1.339 0.175-2.191z" fill="#000" fill-opacity=".38"/> + <path d="m275.2 639.35v2.25h-11.437v-1.934l5.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.347 0.149-0.407 0.223-0.801 0.223-1.184 0-0.539-0.102-1.012-0.305-1.418-0.195-0.414-0.484-0.738-0.867-0.973-0.383-0.242-0.848-0.363-1.395-0.363-0.632 0-1.164 0.137-1.593 0.41-0.43 0.274-0.754 0.653-0.973 1.137-0.219 0.476-0.328 1.023-0.328 1.641h-2.824c0-0.993 0.226-1.899 0.679-2.719 0.453-0.828 1.11-1.485 1.969-1.969 0.86-0.492 1.895-0.738 3.106-0.738 1.14 0 2.109 0.191 2.906 0.574s1.402 0.926 1.816 1.629c0.422 0.703 0.633 1.535 0.633 2.496 0 0.531-0.086 1.059-0.258 1.582s-0.418 1.047-0.738 1.57c-0.313 0.516-0.684 1.035-1.113 1.559-0.43 0.516-0.903 1.039-1.418 1.57l-3.692 4.067zm13.575-7.711v2.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="m271.47 702.18v17.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.016-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.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.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.426 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.743 0.258 2.813zm-2.825 3.187v-3.609c0-0.68-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.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.368 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="m288.77 787.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="m807.17 0s-807.17 0-807.17 134v656.72c0 5.3024 21.491 9.2808 48 9.2808h2304c26.508 0 48-3.9784 48-9.2808v-656.72c0-134-792.84-134-792.84-134h-407.15zm804.84 162.4c-15.464 0-28 2.5072-28 5.6v600.8c0 3.0928 12.536 5.6 28 5.6h175.99c15.464 0 28-2.5072 28-5.6v-600.8c0-3.0928-12.536-5.6-28-5.6z" fill="#000" fill-opacity="0" style="stroke-width:6.9281" tb:tag="clickArea"/> diff --git a/application/src/main/data/json/system/scada_symbols/sand-filter-hp.svg b/application/src/main/data/json/system/scada_symbols/sand-filter-hp.svg index 987f4d3b64..76041feef5 100644 --- a/application/src/main/data/json/system/scada_symbols/sand-filter-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/sand-filter-hp.svg @@ -1,4 +1,5 @@ -<svg xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="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" id="svg100" sodipodi:docname="sand-filter.svg" inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"><tb:metadata xmlns=""><![CDATA[{ +<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[{ "title": "HP Sand filter", "description": "Sand filter with configurable filtration mode option and various states.", "searchTags": [ @@ -416,7 +417,7 @@ }, { "id": "runningColor", - "name": "runningColor", + "name": "{i18n:scada.symbol.running-color}", "type": "color", "default": "#FFFFFF", "required": null, @@ -576,61 +577,53 @@ } ] } - - - - - - - - - - - - - - - - - - - - Filter - - - - - Backwash - - - - - Rinse - - - - - Waste - - - - - Recirculate - - - - - Closed - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Filter + + + Backwash + + + Rinse + + + Waste + + + Recirculate + + + Closed + + diff --git a/application/src/main/data/json/system/scada_symbols/sand-filter.svg b/application/src/main/data/json/system/scada_symbols/sand-filter.svg index 59aa228a6f..243d5ed6e8 100644 --- a/application/src/main/data/json/system/scada_symbols/sand-filter.svg +++ b/application/src/main/data/json/system/scada_symbols/sand-filter.svg @@ -1,5 +1,5 @@ - -{ +<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[{ "title": "Sand filter", "description": "Sand filter with configurable filtration mode option and various states.", "searchTags": [ @@ -12,7 +12,7 @@ "tags": [ { "tag": "background", - "stateRenderFunction": "var color = ctx.properties.standFilterColor;\nelement.attr({fill: color});", + "stateRenderFunction": "var color = ctx.properties.sandFilterColor;\nelement.attr({fill: color});", "actions": null }, { @@ -253,8 +253,8 @@ "step": null }, { - "id": "standFilterColor", - "name": "{i18n:scada.symbol.stand-filter-color}", + "id": "sandFilterColor", + "name": "{i18n:scada.symbol.sand-filter-color}", "type": "color", "default": "#FFFFFF", "required": null, @@ -349,44 +349,99 @@ "step": null } ] -} +}]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - Filter + Filter - Backwash + Backwash - Rinse + Rinse - Waste + Waste - Recirculate + Recirculate - Closed + Closed - + + @@ -646,4 +701,4 @@ - \ No newline at end of file + diff --git a/application/src/main/data/json/system/scada_symbols/short-vertical-tank-hp.svg b/application/src/main/data/json/system/scada_symbols/short-vertical-tank-hp.svg index 1e75c10e27..3d32e49ab8 100644 --- a/application/src/main/data/json/system/scada_symbols/short-vertical-tank-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/short-vertical-tank-hp.svg @@ -1,5 +1,6 @@ -{ - "title": "Short vertical tank", +<svg width="600" height="600" fill="none" version="1.1" viewBox="0 0 600 600" xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg"> + <tb:metadata><![CDATA[{ + "title": "HP Short vertical tank", "description": "Short vertical tank", "searchTags": [ "short tank" @@ -32,12 +33,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))*570; \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))*594; \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 = 570 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(170, y, 202, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 160, 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(182, minorY, 202, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 3;\n var majorIntervalLength = 594 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(170, y, 202, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n if (i === 0) {\n majorTickText.attr({x: 160, y: y + 10, 'text-anchor': 'end', class: 'majorTickText'});\n } else if (i === majorIntervals) {\n majorTickText.attr({x: 160, y: y - 5, 'text-anchor': 'end', class: 'majorTickText'});\n } else {\n majorTickText.attr({x: 160, y: y + 2, 'text-anchor': 'end', class: 'majorTickText'});\n }\n majorTickText.first().attr({'dominant-baseline': 'middle'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(182, minorY, 202, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "actions": null }, { @@ -454,83 +455,90 @@ } ] } - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - + + + + - + + + - + + - \ No newline at end of file + diff --git a/application/src/main/data/json/system/scada_symbols/simple-horizontal-scale-hp.svg b/application/src/main/data/json/system/scada_symbols/simple-horizontal-scale-hp.svg index 0a2afd9519..1052d1361c 100644 --- a/application/src/main/data/json/system/scada_symbols/simple-horizontal-scale-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/simple-horizontal-scale-hp.svg @@ -1,4 +1,5 @@ -{ +<svg width="800" height="400" fill="none" version="1.1" viewBox="0 0 800 400" xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg"> + <tb:metadata><![CDATA[{ "title": "HP Simple horizontal scale", "description": "Simple horizontal scale with current volume value and configurable warning and critical scale.", "searchTags": [ @@ -18,7 +19,7 @@ }, { "tag": "highCriticalScale", - "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighCriticalValue = ctx.properties.showHighCriticalScale;\nvar highCriticalValue = ctx.properties.highCriticalScale;\n\nif (showHighCriticalValue) {\n element.show();\n var offset = calculateOffset(highCriticalValue, minValue, maxValue);\n var childrenElement = element.children();\n childrenElement[0].height(653-offset);\n childrenElement[1].height(653-offset-1);\n} else {\n element.hide();\n}", + "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 638;\n\n return offset;\n}\n\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighCriticalValue = ctx.properties.showHighCriticalScale;\nvar highCriticalValue = ctx.properties.highCriticalScale;\n\nif (showHighCriticalValue) {\n element.show();\n var offset = calculateOffset(highCriticalValue, minValue, maxValue);\n var childrenElement = element.children();\n childrenElement[0].height(638-offset);\n childrenElement[1].height(638-offset-1);\n} else {\n element.hide();\n}", "actions": null }, { @@ -28,7 +29,7 @@ }, { "tag": "highWarningScale", - "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighWarningScale = ctx.properties.showHighWarningScale;\nvar highWarningValue = ctx.properties.highWarningScale;\nif (showHighWarningScale) {\n element.show();\n var offset = calculateOffset(highWarningValue, minValue, maxValue);\n var childrenElement = element.children();\n childrenElement[0].height(653-offset);\n childrenElement[1].height(653-offset-1);\n} else {\n element.hide();\n}", + "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 638;\n\n return offset;\n}\n\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighWarningScale = ctx.properties.showHighWarningScale;\nvar highWarningValue = ctx.properties.highWarningScale;\nif (showHighWarningScale) {\n element.show();\n var offset = calculateOffset(highWarningValue, minValue, maxValue);\n var childrenElement = element.children();\n childrenElement[0].height(638-offset);\n childrenElement[1].height(638-offset-1);\n} else {\n element.hide();\n}", "actions": null }, { @@ -43,7 +44,7 @@ }, { "tag": "lowCriticalScale", - "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowCriticalValue = ctx.properties.lowCriticalScale;\nif (showLowCriticalScale) {\n element.show();\n var offset = calculateOffset(lowCriticalValue, minValue, maxValue);\n var childrenElement = element.children();\n childrenElement[0].height(offset);\n childrenElement[1].height(offset-1);\n} else {\n element.hide();\n}", + "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 638;\n\n return offset;\n}\n\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowCriticalValue = ctx.properties.lowCriticalScale;\nif (showLowCriticalScale) {\n element.show();\n var offset = calculateOffset(lowCriticalValue, minValue, maxValue);\n var childrenElement = element.children();\n childrenElement[0].height(offset);\n childrenElement[1].height(offset-1);\n} else {\n element.hide();\n}", "actions": null }, { @@ -53,7 +54,7 @@ }, { "tag": "lowWarningScale", - "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowWarningScale = ctx.properties.showLowWarningScale;\nvar lowWarningValue = ctx.properties.lowWarningScale;\nif (showLowWarningScale) {\n element.show();\n var offset = calculateOffset(lowWarningValue, minValue, maxValue);\n var childrenElement = element.children();\n childrenElement[0].height(offset);\n childrenElement[1].height(offset-1);\n} else {\n element.hide();\n}", + "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 638;\n\n return offset;\n}\n\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowWarningScale = ctx.properties.showLowWarningScale;\nvar lowWarningValue = ctx.properties.lowWarningScale;\nif (showLowWarningScale) {\n element.show();\n var offset = calculateOffset(lowWarningValue, minValue, maxValue);\n var childrenElement = element.children();\n childrenElement[0].height(offset);\n childrenElement[1].height(offset-1);\n} else {\n element.hide();\n}", "actions": null }, { @@ -83,7 +84,7 @@ }, { "tag": "target", - "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showTarget = ctx.properties.showTarget;\nvar targetValue = ctx.values.targetValue;\n\nif (showTarget && targetValue !== null) {\n element.show();\n var offset = calculateOffset(targetValue, minValue, maxValue);\n element.transform({translateX: -(Math.min(Math.max(offset-17, 0), 617))});\n} else {\n element.hide();\n}\n\n", + "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 640;\n\n return offset;\n}\n\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showTarget = ctx.properties.showTarget;\nvar targetValue = ctx.values.targetValue;\n\nif (showTarget && targetValue !== null) {\n element.show();\n var offset = calculateOffset(targetValue, minValue, maxValue);\n element.transform({translateX: -(640 - (Math.min(Math.max(offset+12, 30), 635)))});\n} else {\n element.hide();\n}\n\n", "actions": null }, { @@ -108,7 +109,7 @@ }, { "tag": "valuePosition", - "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar value = ctx.values.value;\n\nif (value !== null) {\n var offset = calculateOffset(value, minValue, maxValue);\n element.transform({translateX: -offset});\n}\n", + "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 638;\n\n return offset;\n}\n\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar value = ctx.values.value;\n\nif (value !== null) {\n var offset = calculateOffset(value, minValue, maxValue);\n element.transform({translateX: -(638 - offset)});\n}\n", "actions": null } ], @@ -539,7 +540,7 @@ "id": "units", "name": "{i18n:scada.symbol.units}", "type": "units", - "default": "°C", + "default": "&#176;C", "required": null, "subLabel": null, "divider": null, @@ -719,33 +720,39 @@ } ] } -Outdoor°C - 100 - 0 - - - 26 - + Outdoor + °C + + 100 + 0 + + + + 26 + + - - + + - - + + - - + + - - + + - - - - - \ No newline at end of file + + + + + + + diff --git a/application/src/main/data/json/system/scada_symbols/vertical-scale-hp.svg b/application/src/main/data/json/system/scada_symbols/vertical-scale-hp.svg index 82fc59bae2..f1e1f2f178 100644 --- a/application/src/main/data/json/system/scada_symbols/vertical-scale-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/vertical-scale-hp.svg @@ -1,4 +1,5 @@ -{ +<svg width="400" height="800" fill="none" version="1.1" viewBox="0 0 400 800" xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg"> + <tb:metadata><![CDATA[{ "title": "HP Simple vertical scale", "description": "Simple vertical scale with current volume value and configurable warning and critical scale.", "searchTags": [ @@ -539,7 +540,7 @@ "id": "units", "name": "{i18n:scada.symbol.units}", "type": "units", - "default": "°C", + "default": "&#176;C", "required": null, "subLabel": null, "divider": null, @@ -719,7 +720,9 @@ } ] } -Outdoor°C + Outdoor + °C + @@ -738,14 +741,18 @@ - - 100 - 0 - + + + 100 + 0 + + 26 - + + - - \ No newline at end of file + + + diff --git a/application/src/main/data/json/system/scada_symbols/vertical-tank-hp.svg b/application/src/main/data/json/system/scada_symbols/vertical-tank-hp.svg index 4749dc5ce2..33148d66a5 100644 --- a/application/src/main/data/json/system/scada_symbols/vertical-tank-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/vertical-tank-hp.svg @@ -1,5 +1,6 @@ -{ - "title": "Vertical tank", +<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[{ + "title": "HP Vertical tank", "description": "Vertical tank", "searchTags": [ "vertical tank" @@ -454,83 +455,90 @@ } ] } - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - + + + + - + + + - + + - \ No newline at end of file + diff --git a/application/src/main/data/json/system/scada_symbols/vertical-valve-hp.svg b/application/src/main/data/json/system/scada_symbols/vertical-valve-hp.svg index 41ab891821..370bcb38ba 100644 --- a/application/src/main/data/json/system/scada_symbols/vertical-valve-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/vertical-valve-hp.svg @@ -1,4 +1,5 @@ -{ +<svg width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg"> + <tb:metadata><![CDATA[{ "title": "HP Vertical valve", "description": "Vertical valve with various states.", "searchTags": [ @@ -17,7 +18,7 @@ "stateRenderFunction": null, "actions": { "click": { - "actionFunction": "ctx.api.callAction(event, 'click');" + "actionFunction": "var opened = ctx.values.opened;\nvar action = opened ? 'close' : 'open';\n\nctx.api.callAction(event, action, undefined, {\n next: () => {\n ctx.api.setValue('opened', !opened);\n }\n});" } } }, @@ -76,6 +77,74 @@ "defaultSetValueSettings": null, "defaultWidgetActionSettings": null }, + { + "id": "open", + "name": "{i18n:scada.symbol.open}", + "hint": "{i18n:scada.symbol.open-hint}", + "group": null, + "type": "action", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": null, + "defaultSetValueSettings": { + "action": "SET_ATTRIBUTE", + "executeRpc": { + "method": "setState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "setAttribute": { + "scope": "SHARED_SCOPE", + "key": "open" + }, + "putTimeSeries": { + "key": "state" + }, + "valueToData": { + "type": "CONSTANT", + "constantValue": true, + "valueToDataFunction": "/* Convert input boolean value to RPC parameters or attribute/time-series value */\nreturn value;" + } + }, + "defaultWidgetActionSettings": null + }, + { + "id": "close", + "name": "{i18n:scada.symbol.close}", + "hint": "{i18n:scada.symbol.close-hint}", + "group": null, + "type": "action", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": null, + "defaultSetValueSettings": { + "action": "SET_ATTRIBUTE", + "executeRpc": { + "method": "setState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "setAttribute": { + "scope": "SHARED_SCOPE", + "key": "open" + }, + "putTimeSeries": { + "key": "state" + }, + "valueToData": { + "type": "CONSTANT", + "constantValue": false, + "valueToDataFunction": "/* Convert input boolean value to RPC parameters or attribute/time-series value */\nreturn value;" + } + }, + "defaultWidgetActionSettings": null + }, { "id": "warning", "name": "{i18n:scada.symbol.warning-state}", @@ -220,26 +289,6 @@ "setEntityId": false, "stateEntityParamName": null } - }, - { - "id": "click", - "name": "{i18n:scada.symbol.on-click}", - "hint": "{i18n:scada.symbol.on-click-hint}", - "group": null, - "type": "widgetAction", - "valueType": "BOOLEAN", - "trueLabel": null, - "falseLabel": null, - "stateLabel": null, - "defaultGetValueSettings": null, - "defaultSetValueSettings": null, - "defaultWidgetActionSettings": { - "type": "doNothing", - "targetDashboardStateId": null, - "openRightLayout": false, - "setEntityId": false, - "stateEntityParamName": null - } } ], "properties": [ @@ -309,20 +358,26 @@ } ] } - - - + + + + - + + + + + - + + - \ No newline at end of file + diff --git a/application/src/main/data/json/system/widget_types/update_location_timeseries.json b/application/src/main/data/json/system/widget_types/update_location_timeseries.json index 03d38ece27..9724e8e6b6 100644 --- a/application/src/main/data/json/system/widget_types/update_location_timeseries.json +++ b/application/src/main/data/json/system/widget_types/update_location_timeseries.json @@ -9,7 +9,7 @@ "sizeX": 7.5, "sizeY": 3, "resources": [], - "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? latLabel : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n\n \n {{ settings.showLabel ? lngLabel : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n\n
\n \n \n \n
\n
\n\n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-timeseries-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-coordinate-specified' | translate }}\n
\n
\n
\n
", + "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? latLabel : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n\n \n {{ settings.showLabel ? lngLabel : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n\n
\n \n \n \n
\n
\n\n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-timeseries-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-coordinate-specified' | translate }}\n
\n
\n
\n
", "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex-direction: column;\n flex: 1;\n}\n\n.grid__element.horizontal-alignment {\n flex-direction: row;\n}\n\n.grid__element:last-child {\n align-items: center;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-button.getLocation {\n margin-right: 10px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.attribute-update-form mat-form-field{\n width: 100%;\n padding-right: 5px;\n}\n\n.attribute-update-form.small-width mat-form-field{\n width: 150px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}", "controllerScript": "let $scope;\r\nlet settings;\r\nlet attributeService;\r\nlet utils;\r\nlet translate;\r\n\r\nself.onInit = function() {\r\n self.ctx.ngZone.run(function() {\r\n init(); \r\n self.ctx.detectChanges(true);\r\n });\r\n};\r\n\r\n\r\nfunction init() {\r\n $scope = self.ctx.$scope;\r\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\r\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\r\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\r\n $scope.toastTargetId = 'input-widget' + utils.guid();\r\n settings = utils.deepClone(self.ctx.settings) || {};\r\n \r\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\r\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\r\n settings.showGetLocation = utils.defaultValue(settings.showGetLocation, true);\r\n settings.enableHighAccuracy = utils.defaultValue(settings.enableHighAccuracy, false);\r\n $scope.settings = settings;\r\n $scope.isValidParameter = true;\r\n $scope.dataKeyDetected = false; \r\n\r\n $scope.isHorizontal = (settings.inputFieldsAlignment === 'row');\r\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-coordinate-required');\r\n $scope.latLabel = utils.customTranslation(settings.latLabel, settings.latLabel) || translate.instant('widgets.input-widgets.latitude');\r\n $scope.lngLabel = utils.customTranslation(settings.lngLabel, settings.lngLabel) || translate.instant('widgets.input-widgets.longitude');\r\n\r\n $scope.attributeUpdateFormGroup = $scope.fb.group(\r\n {currentLat: [undefined, [$scope.validators.required,\r\n $scope.validators.min(-90),\r\n $scope.validators.max(90)]],\r\n currentLng: [undefined, [$scope.validators.required,\r\n $scope.validators.min(-180),\r\n $scope.validators.max(180)]]}\r\n );\r\n\r\n if (self.ctx.datasources && self.ctx.datasources.length) {\r\n var datasource = self.ctx.datasources[0];\r\n if (datasource.type === 'entity') {\r\n if (datasource.entityType && datasource.entityId) {\r\n $scope.entityName = datasource.entityName;\r\n if (settings.widgetTitle && settings.widgetTitle.length) {\r\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\r\n } else {\r\n $scope.titleTemplate = self.ctx.widgetConfig.title;\r\n }\r\n\r\n $scope.entityDetected = true;\r\n }\r\n }\r\n if (datasource.dataKeys.length > 1) {\r\n $scope.dataKeyDetected = true;\r\n for (let i = 0; i < datasource.dataKeys.length; i++) {\r\n if (datasource.dataKeys[i].type != \"timeseries\"){\r\n $scope.isValidParameter = false;\r\n }\r\n if (datasource.dataKeys[i].name !== settings.latKeyName && datasource.dataKeys[i].name !== settings.lngKeyName){\r\n $scope.dataKeyDetected = false;\r\n }\r\n }\r\n }\r\n }\r\n\r\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\r\n\r\n $scope.updateAttribute = function () {\r\n $scope.isFocused = false;\r\n if ($scope.entityDetected) {\r\n var datasource = self.ctx.datasources[0];\r\n\r\n attributeService.saveEntityTimeseries(\r\n datasource.entity.id,\r\n 'scope',\r\n [\r\n {\r\n key: settings.latKeyName,\r\n value: $scope.attributeUpdateFormGroup.get('currentLat').value\r\n },{\r\n key: settings.lngKeyName,\r\n value: $scope.attributeUpdateFormGroup.get('currentLng').value\r\n }\r\n ]\r\n ).subscribe(\r\n function success() {\r\n $scope.originalLat = $scope.attributeUpdateFormGroup.get('currentLat').value;\r\n $scope.originalLng = $scope.attributeUpdateFormGroup.get('currentLng').value;\r\n if (settings.showResultMessage) {\r\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\r\n }\r\n },\r\n function fail() {\r\n if (settings.showResultMessage) {\r\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\r\n }\r\n }\r\n );\r\n }\r\n };\r\n\r\n $scope.changeFocus = function () {\r\n if ($scope.attributeUpdateFormGroup.get('currentLat').value === $scope.originalLat && $scope.attributeUpdateFormGroup.get('currentLng').value === $scope.originalLng) {\r\n $scope.isFocused = false;\r\n }\r\n };\r\n \r\n $scope.discardChange = function() {\r\n $scope.attributeUpdateFormGroup.setValue({\r\n 'currentLat': $scope.originalLat,\r\n 'currentLng': $scope.originalLng\r\n });\r\n $scope.isFocused = false;\r\n $scope.attributeUpdateFormGroup.markAsPristine();\r\n self.onDataUpdated();\r\n };\r\n \r\n $scope.disableButton = function () {\r\n return $scope.attributeUpdateFormGroup.get('currentLat').value === $scope.originalLat && $scope.attributeUpdateFormGroup.get('currentLng').value === $scope.originalLng || $scope.currentLng === null || $scope.currentLat === null;\r\n };\r\n \r\n $scope.getCoordinate = function() {\r\n if (navigator.geolocation) {\r\n navigator.geolocation.getCurrentPosition(showPosition, function (){\r\n $scope.showErrorToast(translate.instant('widgets.input-widgets.blocked-location'), \r\n 'bottom', 'left', $scope.toastTargetId);\r\n }, {\r\n enableHighAccuracy: settings.enableHighAccuracy\r\n });\r\n } else {\r\n $scope.showErrorToast(translate.instant('widgets.input-widgets.no-support-geolocation'), 'bottom', 'left', $scope.toastTargetId);\r\n }\r\n };\r\n \r\n function showPosition(position) {\r\n $scope.attributeUpdateFormGroup.setValue({\r\n currentLat: correctValue(position.coords.latitude),\r\n currentLng: correctValue(position.coords.longitude)\r\n });\r\n $scope.attributeUpdateFormGroup.markAsDirty();\r\n $scope.isFocused = true;\r\n }\r\n \r\n self.onResize();\r\n}\r\n\r\nself.onDataUpdated = function() {\r\n try {\r\n if ($scope.dataKeyDetected) {\r\n if (!$scope.isFocused) {\r\n for(let i = 0; i < self.typeParameters().maxDataKeys; i++){\r\n if(self.ctx.data[i].dataKey.name === self.ctx.settings.latKeyName && $scope.attributeUpdateFormGroup.get('currentLat').pristine){\r\n $scope.originalLat = self.ctx.data[i].data[0][1];\r\n $scope.attributeUpdateFormGroup.get('currentLat').patchValue(correctValue($scope.originalLat));\r\n } else if(self.ctx.data[i].dataKey.name === self.ctx.settings.lngKeyName && $scope.attributeUpdateFormGroup.get('currentLng').pristine){\r\n $scope.originalLng = self.ctx.data[i].data[0][1];\r\n $scope.attributeUpdateFormGroup.get('currentLng').patchValue(correctValue($scope.originalLng));\r\n }\r\n }\r\n self.ctx.detectChanges();\r\n }\r\n }\r\n } catch (e) {\r\n console.log(e);\r\n }\r\n};\r\n\r\nfunction correctValue(value) {\r\n if (typeof value !== \"number\") {\r\n return 0;\r\n }\r\n return value;\r\n}\r\n\r\nself.onResize = function() {\r\n $scope.smallWidthContainer = (self.ctx.$container && self.ctx.$container[0].offsetWidth < 320);\r\n $scope.changeAlignment = ($scope.isHorizontal && self.ctx.$container && self.ctx.$container[0].offsetWidth < 480);\r\n self.ctx.detectChanges();\r\n};\r\n\r\nself.typeParameters = function() {\r\n return {\r\n maxDatasources: 1,\r\n maxDataKeys: 2,\r\n singleEntity: true\r\n };\r\n};\r\n\r\nself.onDestroy = function() {\r\n\r\n};", "settingsSchema": "", diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index cc9c989cfa..8470566af3 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -90,6 +90,7 @@ import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.HasId; import org.thingsboard.server.common.data.id.MobileAppBundleId; import org.thingsboard.server.common.data.id.MobileAppId; +import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.common.data.id.QueueId; @@ -105,6 +106,7 @@ import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.id.WidgetsBundleId; import org.thingsboard.server.common.data.mobile.app.MobileApp; import org.thingsboard.server.common.data.mobile.bundle.MobileAppBundle; +import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.SortOrder; @@ -141,6 +143,7 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.mobile.MobileAppBundleService; import org.thingsboard.server.dao.mobile.MobileAppService; import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.notification.NotificationTargetService; import org.thingsboard.server.dao.oauth2.OAuth2ClientService; import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService; import org.thingsboard.server.dao.ota.OtaPackageService; @@ -355,6 +358,9 @@ public abstract class BaseController { @Autowired protected TbServiceInfoProvider serviceInfoProvider; + @Autowired + protected NotificationTargetService notificationTargetService; + @Value("${server.log_controller_error_stack_trace}") @Getter private boolean logControllerErrorStackTrace; @@ -852,6 +858,10 @@ public abstract class BaseController { return checkEntityId(mobileAppBundleId, mobileAppBundleService::findMobileAppBundleById, operation); } + NotificationTarget checkNotificationTargetId(NotificationTargetId notificationTargetId, Operation operation) throws ThingsboardException { + return checkEntityId(notificationTargetId, notificationTargetService::findNotificationTargetById, operation); + } + protected I emptyId(EntityType entityType) { return (I) EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID); } diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java index c1248a1fbf..e055c6d6ae 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java @@ -266,6 +266,12 @@ public class NotificationController extends BaseController { } notificationRequest.setTenantId(user.getTenantId()); checkEntity(notificationRequest.getId(), notificationRequest, NOTIFICATION); + List targets = notificationRequest.getTargets().stream() + .map(NotificationTargetId::new) + .toList(); + for (NotificationTargetId targetId : targets) { + checkNotificationTargetId(targetId, Operation.READ); + } notificationRequest.setOriginatorEntityId(user.getId()); notificationRequest.setInfo(null); @@ -316,6 +322,8 @@ public class NotificationController extends BaseController { Map recipientsCountByTarget = new LinkedHashMap<>(); Map firstRecipient = new HashMap<>(); for (NotificationTarget target : targets) { + checkEntity(getCurrentUser(), target, Operation.READ); + int recipientsCount; List recipientsPart; NotificationTargetType targetType = target.getConfiguration().getType(); diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java index 7634819e39..6a45b9e4e5 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java @@ -127,7 +127,7 @@ public class NotificationTargetController extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") public NotificationTarget getNotificationTargetById(@PathVariable UUID id) throws ThingsboardException { NotificationTargetId notificationTargetId = new NotificationTargetId(id); - return checkEntityId(notificationTargetId, notificationTargetService::findNotificationTargetById, Operation.READ); + return checkNotificationTargetId(notificationTargetId, Operation.READ); } @ApiOperation(value = "Get recipients for notification target config (getRecipientsForNotificationTargetConfig)", @@ -214,7 +214,7 @@ public class NotificationTargetController extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") public void deleteNotificationTargetById(@PathVariable UUID id) throws Exception { NotificationTargetId notificationTargetId = new NotificationTargetId(id); - NotificationTarget notificationTarget = checkEntityId(notificationTargetId, notificationTargetService::findNotificationTargetById, Operation.DELETE); + NotificationTarget notificationTarget = checkNotificationTargetId(notificationTargetId, Operation.DELETE); doDeleteAndLog(EntityType.NOTIFICATION_TARGET, notificationTarget, notificationTargetService::deleteNotificationTargetById); } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java index 71e2b576ed..311fe6cb2c 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java @@ -40,7 +40,9 @@ import org.thingsboard.server.common.data.notification.NotificationRequestConfig import org.thingsboard.server.common.data.notification.NotificationRequestStats; import org.thingsboard.server.common.data.notification.NotificationRequestStatus; import org.thingsboard.server.common.data.notification.NotificationStatus; +import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.common.data.notification.info.GeneralNotificationInfo; +import org.thingsboard.server.common.data.notification.info.NotificationInfo; import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo; import org.thingsboard.server.common.data.notification.settings.NotificationSettings; import org.thingsboard.server.common.data.notification.settings.UserNotificationSettings; @@ -217,6 +219,21 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple } } + @Override + public void sendSystemNotification(TenantId tenantId, NotificationTargetId targetId, NotificationType type, NotificationInfo info) { + log.debug("[{}] Sending {} system notification to {}: {}", tenantId, type, targetId, info); + NotificationTemplate notificationTemplate = notificationTemplateService.findTenantOrSystemNotificationTemplate(tenantId, type) + .orElseThrow(() -> new IllegalArgumentException("No notification template found for type " + type)); + NotificationRequest notificationRequest = NotificationRequest.builder() + .tenantId(TenantId.SYS_TENANT_ID) + .targets(List.of(targetId.getId())) + .templateId(notificationTemplate.getId()) + .info(info) + .originatorEntityId(TenantId.SYS_TENANT_ID) + .build(); + processNotificationRequest(TenantId.SYS_TENANT_ID, notificationRequest, null); + } + private void processNotificationRequestAsync(NotificationProcessingContext ctx, List targets, FutureCallback callback) { notificationExecutor.submit(() -> { long startTs = System.currentTimeMillis(); @@ -271,7 +288,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple }, 256); } else { recipients = new PageDataIterable<>(pageLink -> { - return notificationTargetService.findRecipientsForNotificationTargetConfig(ctx.getTenantId(), targetConfig, pageLink); + return notificationTargetService.findRecipientsForNotificationTargetConfig(target.getTenantId(), targetConfig, pageLink); }, 256); } } diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java index 3302fe4fc2..aef4772053 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -23,6 +23,7 @@ import com.google.common.util.concurrent.ListeningExecutorService; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.RandomStringUtils; import org.awaitility.Awaitility; import org.hamcrest.Matcher; import org.hibernate.exception.ConstraintViolationException; @@ -104,6 +105,21 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.notification.NotificationType; +import org.thingsboard.server.common.data.notification.targets.NotificationTarget; +import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig; +import org.thingsboard.server.common.data.notification.targets.platform.UserListFilter; +import org.thingsboard.server.common.data.notification.targets.platform.UsersFilter; +import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; +import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate; +import org.thingsboard.server.common.data.notification.template.HasSubject; +import org.thingsboard.server.common.data.notification.template.MobileAppDeliveryMethodNotificationTemplate; +import org.thingsboard.server.common.data.notification.template.NotificationTemplate; +import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig; +import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate; +import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.oauth2.MapperType; import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig; @@ -121,6 +137,7 @@ import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.msg.session.FeatureType; import org.thingsboard.server.config.ThingsboardSecurityConfiguration; import org.thingsboard.server.dao.Dao; +import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.device.ClaimDevicesService; import org.thingsboard.server.dao.tenant.TenantProfileService; @@ -142,6 +159,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; @@ -1189,4 +1207,76 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { return readResponse(mockMvc.perform(request).andExpect(status().isOk()), TbResourceInfo.class); } + protected NotificationTarget createNotificationTarget(UserId... usersIds) { + UserListFilter filter = new UserListFilter(); + filter.setUsersIds(DaoUtil.toUUIDs(List.of(usersIds))); + return createNotificationTarget(filter); + } + + protected NotificationTarget createNotificationTarget(UsersFilter usersFilter) { + NotificationTarget notificationTarget = new NotificationTarget(); + notificationTarget.setName(usersFilter.toString() + RandomStringUtils.randomNumeric(5)); + PlatformUsersNotificationTargetConfig targetConfig = new PlatformUsersNotificationTargetConfig(); + targetConfig.setUsersFilter(usersFilter); + notificationTarget.setConfiguration(targetConfig); + return saveNotificationTarget(notificationTarget); + } + + protected NotificationTarget saveNotificationTarget(NotificationTarget notificationTarget) { + return doPost("/api/notification/target", notificationTarget, NotificationTarget.class); + } + + protected NotificationTemplate createNotificationTemplate(NotificationType notificationType, String subject, + String text, NotificationDeliveryMethod... deliveryMethods) { + NotificationTemplate notificationTemplate = new NotificationTemplate(); + notificationTemplate.setTenantId(tenantId); + notificationTemplate.setName("Notification template: " + text); + notificationTemplate.setNotificationType(notificationType); + NotificationTemplateConfig config = new NotificationTemplateConfig(); + config.setDeliveryMethodsTemplates(new HashMap<>()); + for (NotificationDeliveryMethod deliveryMethod : deliveryMethods) { + DeliveryMethodNotificationTemplate deliveryMethodNotificationTemplate; + switch (deliveryMethod) { + case WEB: { + deliveryMethodNotificationTemplate = new WebDeliveryMethodNotificationTemplate(); + break; + } + case EMAIL: { + deliveryMethodNotificationTemplate = new EmailDeliveryMethodNotificationTemplate(); + break; + } + case SMS: { + deliveryMethodNotificationTemplate = new SmsDeliveryMethodNotificationTemplate(); + break; + } + case MOBILE_APP: + deliveryMethodNotificationTemplate = new MobileAppDeliveryMethodNotificationTemplate(); + break; + default: + throw new IllegalArgumentException("Unsupported delivery method " + deliveryMethod); + } + deliveryMethodNotificationTemplate.setEnabled(true); + deliveryMethodNotificationTemplate.setBody(text); + if (deliveryMethodNotificationTemplate instanceof HasSubject) { + ((HasSubject) deliveryMethodNotificationTemplate).setSubject(subject); + } + config.getDeliveryMethodsTemplates().put(deliveryMethod, deliveryMethodNotificationTemplate); + } + notificationTemplate.setConfiguration(config); + return saveNotificationTemplate(notificationTemplate); + } + + protected NotificationTemplate saveNotificationTemplate(NotificationTemplate notificationTemplate) { + return doPost("/api/notification/template", notificationTemplate, NotificationTemplate.class); + } + + protected List getMyNotifications(boolean unreadOnly, int limit) throws Exception { + return getMyNotifications(NotificationDeliveryMethod.WEB, unreadOnly, limit); + } + + protected List getMyNotifications(NotificationDeliveryMethod deliveryMethod, boolean unreadOnly, int limit) throws Exception { + return doGetTypedWithPageLink("/api/notifications?unreadOnly={unreadOnly}&deliveryMethod={deliveryMethod}&", new TypeReference>() {}, + new PageLink(limit, 0), unreadOnly, deliveryMethod).getData(); + } + } diff --git a/application/src/test/java/org/thingsboard/server/edge/NotificationEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/NotificationEdgeTest.java index 6d4ebb3676..3dab0a369a 100644 --- a/application/src/test/java/org/thingsboard/server/edge/NotificationEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/NotificationEdgeTest.java @@ -274,14 +274,6 @@ public class NotificationEdgeTest extends AbstractEdgeTest { return saveNotificationRule(notificationRule); } - private NotificationTemplate saveNotificationTemplate(NotificationTemplate notificationTemplate) { - return doPost("/api/notification/template", notificationTemplate, NotificationTemplate.class); - } - - private NotificationTarget saveNotificationTarget(NotificationTarget notificationTarget) { - return doPost("/api/notification/target", notificationTarget, NotificationTarget.class); - } - private NotificationRule saveNotificationRule(NotificationRule notificationRule) { return doPost("/api/notification/rule", notificationRule, NotificationRule.class); } diff --git a/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java index bbe31f3e94..fcdf82bd52 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java @@ -30,8 +30,6 @@ import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.NotificationTemplateId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UUIDBased; -import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequestConfig; @@ -44,18 +42,7 @@ import org.thingsboard.server.common.data.notification.rule.NotificationRuleInfo import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.settings.NotificationDeliveryMethodConfig; import org.thingsboard.server.common.data.notification.settings.NotificationSettings; -import org.thingsboard.server.common.data.notification.targets.NotificationTarget; -import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig; -import org.thingsboard.server.common.data.notification.targets.platform.UserListFilter; -import org.thingsboard.server.common.data.notification.targets.platform.UsersFilter; -import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; -import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate; -import org.thingsboard.server.common.data.notification.template.HasSubject; -import org.thingsboard.server.common.data.notification.template.MobileAppDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.NotificationTemplate; -import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig; -import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate; -import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; @@ -71,7 +58,6 @@ import org.thingsboard.server.dao.sqlts.insert.sql.SqlPartitioningRepository; import java.net.URISyntaxException; import java.util.Arrays; -import java.util.HashMap; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -120,25 +106,6 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest notificationSettingsService.deleteNotificationSettings(TenantId.SYS_TENANT_ID); } - protected NotificationTarget createNotificationTarget(UserId... usersIds) { - UserListFilter filter = new UserListFilter(); - filter.setUsersIds(DaoUtil.toUUIDs(List.of(usersIds))); - return createNotificationTarget(filter); - } - - protected NotificationTarget createNotificationTarget(UsersFilter usersFilter) { - NotificationTarget notificationTarget = new NotificationTarget(); - notificationTarget.setName(usersFilter.toString()); - PlatformUsersNotificationTargetConfig targetConfig = new PlatformUsersNotificationTargetConfig(); - targetConfig.setUsersFilter(usersFilter); - notificationTarget.setConfiguration(targetConfig); - return saveNotificationTarget(notificationTarget); - } - - protected NotificationTarget saveNotificationTarget(NotificationTarget notificationTarget) { - return doPost("/api/notification/target", notificationTarget, NotificationTarget.class); - } - protected NotificationRequest submitNotificationRequest(NotificationTargetId targetId, String text, NotificationDeliveryMethod... deliveryMethods) { return submitNotificationRequest(targetId, text, 0, deliveryMethods); } @@ -178,50 +145,6 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest return findNotificationRequest(notificationRequestId).getStats(); } - protected NotificationTemplate createNotificationTemplate(NotificationType notificationType, String subject, - String text, NotificationDeliveryMethod... deliveryMethods) { - NotificationTemplate notificationTemplate = new NotificationTemplate(); - notificationTemplate.setTenantId(tenantId); - notificationTemplate.setName("Notification template: " + text); - notificationTemplate.setNotificationType(notificationType); - NotificationTemplateConfig config = new NotificationTemplateConfig(); - config.setDeliveryMethodsTemplates(new HashMap<>()); - for (NotificationDeliveryMethod deliveryMethod : deliveryMethods) { - DeliveryMethodNotificationTemplate deliveryMethodNotificationTemplate; - switch (deliveryMethod) { - case WEB: { - deliveryMethodNotificationTemplate = new WebDeliveryMethodNotificationTemplate(); - break; - } - case EMAIL: { - deliveryMethodNotificationTemplate = new EmailDeliveryMethodNotificationTemplate(); - break; - } - case SMS: { - deliveryMethodNotificationTemplate = new SmsDeliveryMethodNotificationTemplate(); - break; - } - case MOBILE_APP: - deliveryMethodNotificationTemplate = new MobileAppDeliveryMethodNotificationTemplate(); - break; - default: - throw new IllegalArgumentException("Unsupported delivery method " + deliveryMethod); - } - deliveryMethodNotificationTemplate.setEnabled(true); - deliveryMethodNotificationTemplate.setBody(text); - if (deliveryMethodNotificationTemplate instanceof HasSubject) { - ((HasSubject) deliveryMethodNotificationTemplate).setSubject(subject); - } - config.getDeliveryMethodsTemplates().put(deliveryMethod, deliveryMethodNotificationTemplate); - } - notificationTemplate.setConfiguration(config); - return saveNotificationTemplate(notificationTemplate); - } - - protected NotificationTemplate saveNotificationTemplate(NotificationTemplate notificationTemplate) { - return doPost("/api/notification/template", notificationTemplate, NotificationTemplate.class); - } - protected void saveNotificationSettings(NotificationSettings notificationSettings) throws Exception { doPost("/api/notification/settings", notificationSettings).andExpect(status().isOk()); } @@ -258,15 +181,6 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest doDelete("/api/notification/request/" + id); } - protected List getMyNotifications(boolean unreadOnly, int limit) throws Exception { - return getMyNotifications(NotificationDeliveryMethod.WEB, unreadOnly, limit); - } - - protected List getMyNotifications(NotificationDeliveryMethod deliveryMethod, boolean unreadOnly, int limit) throws Exception { - return doGetTypedWithPageLink("/api/notifications?unreadOnly={unreadOnly}&deliveryMethod={deliveryMethod}&", new TypeReference>() {}, - new PageLink(limit, 0), unreadOnly, deliveryMethod).getData(); - } - protected NotificationRule createNotificationRule(NotificationRuleTriggerConfig triggerConfig, String subject, String text, NotificationTargetId... targets) { return createNotificationRule(triggerConfig, subject, text, List.of(targets), NotificationDeliveryMethod.WEB); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationSettingsService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationSettingsService.java index 9221dcb2cf..09443e8c63 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationSettingsService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationSettingsService.java @@ -15,11 +15,15 @@ */ package org.thingsboard.server.dao.notification; +import com.fasterxml.jackson.databind.JsonNode; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.common.data.notification.settings.NotificationSettings; import org.thingsboard.server.common.data.notification.settings.UserNotificationSettings; +import java.util.Map; + public interface NotificationSettingsService { void saveNotificationSettings(TenantId tenantId, NotificationSettings settings); @@ -36,4 +40,6 @@ public interface NotificationSettingsService { void updateDefaultNotificationConfigs(TenantId tenantId); + void moveMailTemplatesToNotificationCenter(TenantId tenantId, JsonNode mailTemplates, Map mailTemplatesNames); + } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateService.java index 664f976904..d41295f907 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateService.java @@ -22,7 +22,9 @@ import org.thingsboard.server.common.data.notification.template.NotificationTemp import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import java.util.Collection; import java.util.List; +import java.util.Optional; public interface NotificationTemplateService { @@ -32,7 +34,11 @@ public interface NotificationTemplateService { PageData findNotificationTemplatesByTenantIdAndNotificationTypes(TenantId tenantId, List notificationTypes, PageLink pageLink); - int countNotificationTemplatesByTenantIdAndNotificationTypes(TenantId tenantId, List notificationTypes); + Optional findTenantOrSystemNotificationTemplate(TenantId tenantId, NotificationType notificationType); + + Optional findNotificationTemplateByTenantIdAndType(TenantId tenantId, NotificationType notificationType); + + int countNotificationTemplatesByTenantIdAndNotificationTypes(TenantId tenantId, Collection notificationTypes); void deleteNotificationTemplateById(TenantId tenantId, NotificationTemplateId id); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java index ec66a3d53a..1ddc17f36a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java @@ -15,6 +15,12 @@ */ package org.thingsboard.server.common.data.notification; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor public enum NotificationType { GENERAL, @@ -31,6 +37,9 @@ public enum NotificationType { RATE_LIMITS, EDGE_CONNECTION, EDGE_COMMUNICATION_FAILURE, - TASK_PROCESSING_FAILURE + TASK_PROCESSING_FAILURE; + + @Getter + private boolean system; // for future use and compatibility with PE } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java index 10438c484a..1404eaff67 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java @@ -15,8 +15,11 @@ */ package org.thingsboard.server.dao.notification; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; @@ -41,7 +44,9 @@ import org.thingsboard.server.common.data.notification.targets.platform.SystemAd import org.thingsboard.server.common.data.notification.targets.platform.TenantAdministratorsFilter; import org.thingsboard.server.common.data.notification.targets.platform.UsersFilter; import org.thingsboard.server.common.data.notification.targets.platform.UsersFilterType; +import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.NotificationTemplate; +import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.settings.UserSettings; import org.thingsboard.server.common.data.settings.UserSettingsType; @@ -249,6 +254,52 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS } } + @Override + public void moveMailTemplatesToNotificationCenter(TenantId tenantId, JsonNode mailTemplates, Map mailTemplatesNames) { + mailTemplatesNames.forEach((mailTemplateName, notificationType) -> { + moveMailTemplateToNotificationCenter(tenantId, mailTemplates, mailTemplateName, notificationType); + }); + } + + private void moveMailTemplateToNotificationCenter(TenantId tenantId, JsonNode mailTemplates, String mailTemplateName, NotificationType notificationType) { + JsonNode mailTemplate = mailTemplates.get(mailTemplateName); + if (mailTemplate == null || mailTemplate.isNull() || !mailTemplate.has("subject") || !mailTemplate.has("body")) { + return; + } + + String subject = mailTemplate.get("subject").asText(); + String body = mailTemplate.get("body").asText(); + body = body.replace("targetEmail", "recipientEmail"); + + NotificationTemplate notificationTemplate = null; + if (tenantId.isSysTenantId()) { + // updating system notification template, not touching tenants' templates + notificationTemplate = notificationTemplateService.findNotificationTemplateByTenantIdAndType(tenantId, notificationType).orElse(null); + } + if (notificationTemplate == null) { + log.debug("[{}] Creating {} template", tenantId, notificationType); + notificationTemplate = new NotificationTemplate(); + } else { + log.debug("[{}] Updating {} template", tenantId, notificationType); + } + String name = StringUtils.capitalize(notificationType.name().toLowerCase().replaceAll("_", " ")) + " notification"; + notificationTemplate.setName(name); + notificationTemplate.setTenantId(tenantId); + notificationTemplate.setNotificationType(notificationType); + NotificationTemplateConfig notificationTemplateConfig = new NotificationTemplateConfig(); + + EmailDeliveryMethodNotificationTemplate emailNotificationTemplate = new EmailDeliveryMethodNotificationTemplate(); + emailNotificationTemplate.setEnabled(true); + emailNotificationTemplate.setSubject(subject); + emailNotificationTemplate.setBody(body); + + notificationTemplateConfig.setDeliveryMethodsTemplates(Map.of(NotificationDeliveryMethod.EMAIL, emailNotificationTemplate)); + notificationTemplate.setConfiguration(notificationTemplateConfig); + notificationTemplateService.saveNotificationTemplate(tenantId, notificationTemplate); + + ((ObjectNode) mailTemplates).remove(mailTemplateName); + } + private boolean isNotificationConfigured(TenantId tenantId, NotificationType... notificationTypes) { return notificationTemplateService.countNotificationTemplatesByTenantIdAndNotificationTypes(tenantId, List.of(notificationTypes)) > 0; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java index a6ab6e08ff..9b5b9ca85b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java @@ -109,7 +109,7 @@ public class DefaultNotificationTargetService extends AbstractEntityService impl NotificationTarget notificationTarget = findNotificationTargetById(tenantId, targetId); Objects.requireNonNull(notificationTarget, "Notification target [" + targetId + "] not found"); NotificationTargetConfig configuration = notificationTarget.getConfiguration(); - return findRecipientsForNotificationTargetConfig(tenantId, (PlatformUsersNotificationTargetConfig) configuration, pageLink); + return findRecipientsForNotificationTargetConfig(notificationTarget.getTenantId(), (PlatformUsersNotificationTargetConfig) configuration, pageLink); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java index 14b9d176df..ed2a8d0c91 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java @@ -32,6 +32,7 @@ import org.thingsboard.server.dao.entity.EntityDaoService; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; @@ -50,11 +51,19 @@ public class DefaultNotificationTemplateService extends AbstractEntityService im @Override public NotificationTemplate saveNotificationTemplate(TenantId tenantId, NotificationTemplate notificationTemplate) { + NotificationType notificationType = notificationTemplate.getNotificationType(); if (notificationTemplate.getId() != null) { NotificationTemplate oldNotificationTemplate = findNotificationTemplateById(tenantId, notificationTemplate.getId()); - if (notificationTemplate.getNotificationType() != oldNotificationTemplate.getNotificationType()) { + if (notificationType != oldNotificationTemplate.getNotificationType()) { throw new IllegalArgumentException("Notification type cannot be updated"); } + } else { + if (notificationType.isSystem()) { + int systemTemplatesCount = countNotificationTemplatesByTenantIdAndNotificationTypes(tenantId, List.of(notificationType)); + if (systemTemplatesCount > 0) { + throw new IllegalArgumentException("There can only be one notification template of this type"); + } + } } try { NotificationTemplate savedTemplate = notificationTemplateDao.saveAndFlush(tenantId, notificationTemplate); @@ -75,7 +84,19 @@ public class DefaultNotificationTemplateService extends AbstractEntityService im } @Override - public int countNotificationTemplatesByTenantIdAndNotificationTypes(TenantId tenantId, List notificationTypes) { + public Optional findTenantOrSystemNotificationTemplate(TenantId tenantId, NotificationType notificationType) { + return findNotificationTemplateByTenantIdAndType(tenantId, notificationType) + .or(() -> findNotificationTemplateByTenantIdAndType(TenantId.SYS_TENANT_ID, notificationType)); + } + + @Override + public Optional findNotificationTemplateByTenantIdAndType(TenantId tenantId, NotificationType notificationType) { + return findNotificationTemplatesByTenantIdAndNotificationTypes(tenantId, List.of(notificationType), new PageLink(1)).getData() + .stream().findFirst(); + } + + @Override + public int countNotificationTemplatesByTenantIdAndNotificationTypes(TenantId tenantId, Collection notificationTypes) { return notificationTemplateDao.countByTenantIdAndNotificationTypes(tenantId, notificationTypes); } @@ -86,8 +107,16 @@ public class DefaultNotificationTemplateService extends AbstractEntityService im @Override public void deleteEntity(TenantId tenantId, EntityId id, boolean force) { - if (!force && notificationRequestDao.existsByTenantIdAndStatusAndTemplateId(tenantId, NotificationRequestStatus.SCHEDULED, (NotificationTemplateId) id)) { - throw new IllegalArgumentException("Notification template is referenced by scheduled notification request"); + if (!force) { + if (notificationRequestDao.existsByTenantIdAndStatusAndTemplateId(tenantId, NotificationRequestStatus.SCHEDULED, (NotificationTemplateId) id)) { + throw new IllegalArgumentException("Notification template is referenced by scheduled notification request"); + } + if (tenantId.isSysTenantId()) { + NotificationTemplate notificationTemplate = findNotificationTemplateById(tenantId, (NotificationTemplateId) id); + if (notificationTemplate.getNotificationType().isSystem()) { + throw new IllegalArgumentException("System notification template cannot be deleted"); + } + } } try { notificationTemplateDao.removeById(tenantId, id.getId()); diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateDao.java b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateDao.java index 136bc4d472..d3f79f0ed2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateDao.java @@ -24,13 +24,14 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; import org.thingsboard.server.dao.ExportableEntityDao; +import java.util.Collection; import java.util.List; public interface NotificationTemplateDao extends Dao, ExportableEntityDao { PageData findByTenantIdAndNotificationTypesAndPageLink(TenantId tenantId, List notificationTypes, PageLink pageLink); - int countByTenantIdAndNotificationTypes(TenantId tenantId, List notificationTypes); + int countByTenantIdAndNotificationTypes(TenantId tenantId, Collection notificationTypes); void removeByTenantId(TenantId tenantId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTemplateDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTemplateDao.java index f0ca725951..ae46efb2f7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTemplateDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTemplateDao.java @@ -31,6 +31,7 @@ import org.thingsboard.server.dao.notification.NotificationTemplateDao; import org.thingsboard.server.dao.sql.JpaAbstractDao; import org.thingsboard.server.dao.util.SqlDao; +import java.util.Collection; import java.util.List; import java.util.UUID; @@ -53,7 +54,7 @@ public class JpaNotificationTemplateDao extends JpaAbstractDao notificationTypes) { + public int countByTenantIdAndNotificationTypes(TenantId tenantId, Collection notificationTypes) { return notificationTemplateRepository.countByTenantIdAndNotificationTypes(tenantId.getId(), notificationTypes); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTemplateRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTemplateRepository.java index bb45c10d06..bffdf98d43 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTemplateRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTemplateRepository.java @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.dao.ExportableEntityRepository; import org.thingsboard.server.dao.model.sql.NotificationTemplateEntity; +import java.util.Collection; import java.util.List; import java.util.UUID; @@ -45,7 +46,7 @@ public interface NotificationTemplateRepository extends JpaRepository notificationTypes); + @Param("notificationTypes") Collection notificationTypes); @Transactional @Modifying diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationCenter.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationCenter.java index ce2ff08734..02ee53f3ad 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationCenter.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationCenter.java @@ -18,12 +18,15 @@ package org.thingsboard.rule.engine.api; import com.google.common.util.concurrent.FutureCallback; import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationRequestId; +import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequestStats; +import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.common.data.notification.info.GeneralNotificationInfo; +import org.thingsboard.server.common.data.notification.info.NotificationInfo; import org.thingsboard.server.common.data.notification.targets.platform.UsersFilter; import org.thingsboard.server.common.data.notification.template.NotificationTemplate; @@ -35,6 +38,8 @@ public interface NotificationCenter { void sendGeneralWebNotification(TenantId tenantId, UsersFilter recipients, NotificationTemplate template, GeneralNotificationInfo info); + void sendSystemNotification(TenantId tenantId, NotificationTargetId targetId, NotificationType type, NotificationInfo info); // for future use and compatibility with PE + void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId); void markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId); diff --git a/ui-ngx/src/app/core/settings/settings.effects.ts b/ui-ngx/src/app/core/settings/settings.effects.ts index 1564814a74..3ce59f9f6e 100644 --- a/ui-ngx/src/app/core/settings/settings.effects.ts +++ b/ui-ngx/src/app/core/settings/settings.effects.ts @@ -15,7 +15,7 @@ /// import { ActivationEnd, Router } from '@angular/router'; -import { Injectable } from '@angular/core'; +import { Inject, Injectable } from '@angular/core'; import { select, Store } from '@ngrx/store'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { TranslateService } from '@ngx-translate/core'; @@ -31,6 +31,7 @@ import { updateUserLang } from '@app/core/settings/settings.utils'; import { UtilsService } from '@core/services/utils.service'; import { getCurrentAuthUser } from '@core/auth/auth.selectors'; import { ActionAuthUpdateLastPublicDashboardId } from '../auth/auth.actions'; +import { DOCUMENT } from '@angular/common'; export const SETTINGS_KEY = 'SETTINGS'; @@ -43,7 +44,8 @@ export class SettingsEffects { private router: Router, private localStorageService: LocalStorageService, private titleService: TitleService, - private translate: TranslateService + private translate: TranslateService, + @Inject(DOCUMENT) private document: Document, ) { } @@ -56,7 +58,7 @@ export class SettingsEffects { distinctUntilChanged((a, b) => a?.userLang === b?.userLang), tap(setting => { this.localStorageService.setItem(SETTINGS_KEY, setting); - updateUserLang(this.translate, setting.userLang); + updateUserLang(this.translate, this.document, setting.userLang); }) ), {dispatch: false}); diff --git a/ui-ngx/src/app/core/settings/settings.utils.ts b/ui-ngx/src/app/core/settings/settings.utils.ts index 8f0a261d08..fa7ede8559 100644 --- a/ui-ngx/src/app/core/settings/settings.utils.ts +++ b/ui-ngx/src/app/core/settings/settings.utils.ts @@ -19,7 +19,7 @@ import { TranslateService } from '@ngx-translate/core'; import _moment from 'moment'; import { Observable } from 'rxjs'; -export function updateUserLang(translate: TranslateService, userLang: string, translations = env.supportedLangs): Observable { +export function updateUserLang(translate: TranslateService, document: Document, userLang: string, translations = env.supportedLangs): Observable { let targetLang = userLang; if (!translations) { translations = env.supportedLangs; @@ -37,6 +37,7 @@ export function updateUserLang(translate: TranslateService, userLang: string, tr if (!env.production) { console.log(`Detected supported lang: ${detectedSupportedLang}`); } + document.documentElement.lang = detectedSupportedLang.replace('_', '-'); _moment.locale([detectedSupportedLang]); return translate.use(detectedSupportedLang); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts index ed023c1a0d..efb25a0258 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts @@ -112,6 +112,9 @@ export class TimeSeriesChartWidgetComponent implements OnInit, OnDestroy, AfterV legendKey.dataKey.settings = mergeDeep({} as TimeSeriesChartKeySettings, timeSeriesChartKeyDefaultSettings, legendKey.dataKey.settings); legendKey.dataKey.hidden = legendKey.dataKey.settings.dataHiddenByDefault; + if (this.settings.yAxes[legendKey.dataKey.settings.yAxisId]) { + this.settings.yAxes[legendKey.dataKey.settings.yAxisId].show = !legendKey.dataKey.settings.dataHiddenByDefault; + } }); this.legendKeys = this.legendKeys.filter(legendKey => legendKey.dataKey.settings.showInLegend); if (!this.legendKeys.length) { diff --git a/ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.html b/ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.html index 0cd5247719..f8456d190a 100644 --- a/ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.html +++ b/ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.html @@ -62,6 +62,7 @@ diff --git a/ui-ngx/src/app/shared/components/image/image-gallery.component.html b/ui-ngx/src/app/shared/components/image/image-gallery.component.html index 685c53416c..a45b9a982c 100644 --- a/ui-ngx/src/app/shared/components/image/image-gallery.component.html +++ b/ui-ngx/src/app/shared/components/image/image-gallery.component.html @@ -132,7 +132,7 @@ -
+
diff --git a/ui-ngx/src/app/shared/components/kv-map.component.html b/ui-ngx/src/app/shared/components/kv-map.component.html index 36c523d404..6c1e07eaa6 100644 --- a/ui-ngx/src/app/shared/components/kv-map.component.html +++ b/ui-ngx/src/app/shared/components/kv-map.component.html @@ -25,7 +25,7 @@ placeholder="{{ (keyPlaceholderText ? keyPlaceholderText : 'key-val.key') | translate }}"/> -