diff --git a/application/src/main/data/json/edge/instructions/upgrade/docker/upgrade_db.md b/application/src/main/data/json/edge/instructions/upgrade/docker/upgrade_db.md index a594ebe4e7..386c5f2743 100644 --- a/application/src/main/data/json/edge/instructions/upgrade/docker/upgrade_db.md +++ b/application/src/main/data/json/edge/instructions/upgrade/docker/upgrade_db.md @@ -43,19 +43,6 @@ volumes: Execute the following command to start upgrade process: ```bash -docker compose -f docker-compose-upgrade.yml up +docker compose -f docker-compose-upgrade.yml up --abort-on-container-exit {:copy-code} -``` - -Once upgrade process successfully completed, exit from the docker-compose shell by this combination: - -```text -Ctrl + C -``` - -Execute the following command to stop TB Edge upgrade container: - -```bash -docker compose -f docker-compose-upgrade.yml stop -{:copy-code} -``` +``` \ No newline at end of file diff --git a/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json b/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json index 9adeb4f49e..6701b59e0e 100644 --- a/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json +++ b/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json @@ -33,8 +33,13 @@ }, "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode", "name": "Save Timeseries", + "configurationVersion": 1, "configuration": { - "defaultTTL": 0 + "defaultTTL": 0, + "useServerTs": false, + "processingSettings": { + "type": "ON_EVERY_MESSAGE" + } }, "externalId": null }, @@ -185,4 +190,4 @@ ], "ruleChainConnections": null } -} \ No newline at end of file +} diff --git a/application/src/main/data/json/system/scada_symbols/3-phase-voltage-relay-hp.svg b/application/src/main/data/json/system/scada_symbols/3-phase-voltage-relay-hp.svg new file mode 100644 index 0000000000..f6d881b172 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/3-phase-voltage-relay-hp.svg @@ -0,0 +1,548 @@ +{ + "title": "HP 3 phase voltage relay", + "description": "Three phase voltage relay with various states and inications.", + "searchTags": [ + "energy", + "power", + "protection" + ], + "widgetSizeX": 2, + "widgetSizeY": 2, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "firstPhaseIndicator", + "stateRenderFunction": "if (ctx.values.running) {\n element.stroke(ctx.properties.phaseIndicatorColor);\n if (!ctx.values.firstPhaseVoltage) {\n element.fill('#fff');\n } else {\n element.fill(ctx.properties.phaseIndicatorColor);\n }\n} else {\n element.stroke('#000');\n element.fill(ctx.properties.stoppedColor);\n}", + "actions": null + }, + { + "tag": "firstPhaseValue", + "stateRenderFunction": "if (ctx.values.running) {\n element.show();\n ctx.api.font(element, ctx.properties.currentVoltageFont, ctx.properties.currentVoltageColor);\n ctx.api.text(element, ctx.api.formatValue(ctx.values.firstPhaseVoltage, 0, null, 0));\n} else {\n element.hide();\n}", + "actions": null + }, + { + "tag": "secondPhaseIndicator", + "stateRenderFunction": "if (ctx.values.running) {\n element.stroke(ctx.properties.phaseIndicatorColor);\n if (!ctx.values.secondPhaseVoltage) {\n element.fill('#fff');\n } else {\n element.fill(ctx.properties.phaseIndicatorColor);\n }\n} else {\n element.stroke('#000');\n element.fill(ctx.properties.stoppedColor);\n}", + "actions": null + }, + { + "tag": "secondPhaseValue", + "stateRenderFunction": "if (ctx.values.running) {\n element.show();\n ctx.api.font(element, ctx.properties.currentVoltageFont, ctx.properties.currentVoltageColor);\n ctx.api.text(element, ctx.api.formatValue(ctx.values.secondPhaseVoltage, 0, null, 0));\n} else {\n element.hide();\n}", + "actions": null + }, + { + "tag": "thirdPhaseIndicator", + "stateRenderFunction": "if (ctx.values.running) {\n element.stroke(ctx.properties.phaseIndicatorColor);\n if (!ctx.values.thirdPhaseVoltage) {\n element.fill('#fff');\n } else {\n element.fill(ctx.properties.phaseIndicatorColor);\n }\n} else {\n element.stroke('#000');\n element.fill(ctx.properties.stoppedColor);\n}", + "actions": null + }, + { + "tag": "thirdPhaseValue", + "stateRenderFunction": "if (ctx.values.running) {\n element.show();\n ctx.api.font(element, ctx.properties.currentVoltageFont, ctx.properties.currentVoltageColor);\n ctx.api.text(element, ctx.api.formatValue(ctx.values.thirdPhaseVoltage, 0, null, 0));\n} else {\n element.hide();\n}", + "actions": null + }, + { + "tag": "units", + "stateRenderFunction": "if (ctx.properties.showUnits) {\n element.show();\n ctx.api.font(element, ctx.properties.unitsFont, ctx.properties.unitsColor);\n ctx.api.text(element, ctx.properties.units);\n} else {\n element.hide();\n}", + "actions": null + }, + { + "tag": "value-box", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.valueBoxBackground;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "firstPhaseVoltage", + "name": "{i18n:scada.symbol.first-phase-voltage}", + "hint": "{i18n:scada.symbol.phase-voltage-hint}", + "group": null, + "type": "value", + "valueType": "DOUBLE", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": { + "action": "GET_TIME_SERIES", + "defaultValue": null, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "firstPhaseVoltage" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "secondPhaseVoltage", + "name": "{i18n:scada.symbol.second-phase-voltage}", + "hint": "{i18n:scada.symbol.phase-voltage-hint}", + "group": null, + "type": "value", + "valueType": "DOUBLE", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": { + "action": "GET_TIME_SERIES", + "defaultValue": null, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "secondPhaseVoltage" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "thirdPhaseVoltage", + "name": "{i18n:scada.symbol.third-phase-voltage}", + "hint": "{i18n:scada.symbol.phase-voltage-hint}", + "group": null, + "type": "value", + "valueType": "DOUBLE", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": { + "action": "GET_TIME_SERIES", + "defaultValue": null, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "thirdPhaseVoltage" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "subLabel": "{i18n:scada.symbol.stopped}", + "disabled": false, + "visible": true + }, + { + "id": "valueBoxBackground", + "name": "{i18n:scada.symbol.value-box-background}", + "type": "color", + "default": "#DEDEDE", + "disabled": false, + "visible": true + }, + { + "id": "phaseIndicatorColor", + "name": "{i18n:scada.symbol.phase-indicator-color}", + "type": "color", + "default": "#198038", + "disabled": false, + "visible": true + }, + { + "id": "currentVoltageFont", + "name": "{i18n:scada.symbol.current-voltage-color}", + "type": "font", + "default": { + "size": 32, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "currentVoltageColor", + "name": "{i18n:scada.symbol.current-voltage-color}", + "type": "color", + "default": "#002878", + "disabled": false, + "visible": true + }, + { + "id": "showUnits", + "name": "{i18n:scada.symbol.units}", + "type": "switch", + "default": true, + "disabled": false, + "visible": true + }, + { + "id": "units", + "name": "{i18n:scada.symbol.units}", + "type": "units", + "default": "V", + "disabled": false, + "visible": true + }, + { + "id": "unitsFont", + "name": "{i18n:scada.symbol.units}", + "type": "font", + "default": { + "size": 24, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "unitsColor", + "name": "{i18n:scada.symbol.units}", + "type": "color", + "default": "#000", + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "subLabel": "{i18n:scada.symbol.critical}", + "disabled": false, + "visible": true + } + ] +} +220220220v + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/apartments-hp.svg b/application/src/main/data/json/system/scada_symbols/apartments-hp.svg new file mode 100644 index 0000000000..9e2c965f79 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/apartments-hp.svg @@ -0,0 +1,344 @@ +{ + "title": "HP Apartments", + "description": "Apartments with various states.", + "searchTags": [ + "power", + "energy", + "consumer" + ], + "widgetSizeX": 2, + "widgetSizeY": 2, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "required": null, + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "required": null, + "subLabel": "{i18n:scada.symbol.stopped}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "required": null, + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "required": null, + "subLabel": "{i18n:scada.symbol.critical}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + } + ] +} + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/battery-hp.svg b/application/src/main/data/json/system/scada_symbols/battery-hp.svg new file mode 100644 index 0000000000..8b63a9ab29 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/battery-hp.svg @@ -0,0 +1,473 @@ +{ + "title": "HP Battery", + "description": "Battery with various states and scalable quantity.", + "searchTags": [ + "energy", + "power", + "rechargeable", + "storage", + "lithium-ion", + "ev" + ], + "widgetSizeX": 3, + "widgetSizeY": 1, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "indicator", + "stateRenderFunction": "if (ctx.values.running) {\n element.fill(ctx.properties.runningIndicatorColor);\n} else {\n element.fill(ctx.properties.stoppedIndicatorColor);\n}", + "actions": null + }, + { + "tag": "label", + "stateRenderFunction": "ctx.api.font(element, ctx.properties.labelFont, ctx.properties.labelColor);\nctx.api.text(element, ctx.properties.label);", + "actions": null + }, + { + "tag": "left-bottom-connector", + "stateRenderFunction": "if (!ctx.properties.leftBottomConnector) {\n element.hide();\n}", + "actions": null + }, + { + "tag": "left-connector", + "stateRenderFunction": "if (!ctx.properties.leftConnector) {\n element.hide();\n}", + "actions": null + }, + { + "tag": "left-top-connector", + "stateRenderFunction": "if (!ctx.properties.leftTopConnector) {\n element.hide();\n}", + "actions": null + }, + { + "tag": "right-bottom-connector", + "stateRenderFunction": "if (!ctx.properties.rightBottomConnector) {\n element.hide();\n}", + "actions": null + }, + { + "tag": "right-connector", + "stateRenderFunction": "if (!ctx.properties.rightConnector) {\n element.hide();\n}", + "actions": null + }, + { + "tag": "right-top-connector", + "stateRenderFunction": "if (!ctx.properties.rightTopConnector) {\n element.hide();\n}", + "actions": null + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "subLabel": "{i18n:scada.symbol.stopped}", + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "subLabel": "{i18n:scada.symbol.critical}", + "disabled": false, + "visible": true + }, + { + "id": "runningIndicatorColor", + "name": "{i18n:scada.symbol.indicator-colors}", + "type": "color", + "default": "#198038", + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "stoppedIndicatorColor", + "name": "{i18n:scada.symbol.indicator-colors}", + "type": "color", + "default": "#DEDEDE", + "subLabel": "{i18n:scada.symbol.stopped}", + "disabled": false, + "visible": true + }, + { + "id": "label", + "name": "{i18n:scada.symbol.label}", + "type": "text", + "default": "ON", + "fieldClass": "medium-width", + "disabled": false, + "visible": true + }, + { + "id": "labelFont", + "name": "{i18n:scada.symbol.label}", + "type": "font", + "default": { + "size": 30, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "labelColor", + "name": "{i18n:scada.symbol.label}", + "type": "color", + "default": "#1A1A1A", + "disabled": false, + "visible": true + }, + { + "id": "leftConnector", + "name": "{i18n:scada.symbol.left-connector}", + "group": "{i18n:scada.symbol.connectors-positions}", + "type": "switch", + "default": false, + "disabled": false, + "visible": true + }, + { + "id": "leftTopConnector", + "name": "{i18n:scada.symbol.left-top-connector}", + "group": "{i18n:scada.symbol.connectors-positions}", + "type": "switch", + "default": false, + "disabled": false, + "visible": true + }, + { + "id": "leftBottomConnector", + "name": "{i18n:scada.symbol.left-bottom-connector}", + "group": "{i18n:scada.symbol.connectors-positions}", + "type": "switch", + "default": false, + "disabled": false, + "visible": true + }, + { + "id": "rightConnector", + "name": "{i18n:scada.symbol.right-connector}", + "group": "{i18n:scada.symbol.connectors-positions}", + "type": "switch", + "default": false, + "disabled": false, + "visible": true + }, + { + "id": "rightTopConnector", + "name": "{i18n:scada.symbol.right-top-connector}", + "group": "{i18n:scada.symbol.connectors-positions}", + "type": "switch", + "default": false, + "disabled": false, + "visible": true + }, + { + "id": "rightBottomConnector", + "name": "{i18n:scada.symbol.right-bottom-connector}", + "group": "{i18n:scada.symbol.connectors-positions}", + "type": "switch", + "default": false, + "disabled": false, + "visible": true + } + ] +} + + + + + + + + + ON + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/bottom-flow-meter.svg b/application/src/main/data/json/system/scada_symbols/bottom-flow-meter.svg index 7d22c90abf..794e6e61d9 100644 --- a/application/src/main/data/json/system/scada_symbols/bottom-flow-meter.svg +++ b/application/src/main/data/json/system/scada_symbols/bottom-flow-meter.svg @@ -57,7 +57,7 @@ }, { "tag": "value", - "stateRenderFunction": "var value = ctx.values.value;\nctx.api.text(element, value.toFixed(0));\n", + "stateRenderFunction": "var value = ctx.api.formatValue(ctx.values.value, ctx.properties.valueDecimals, '', false);\nctx.api.text(element, value);\n", "actions": { "click": { "actionFunction": "ctx.api.callAction(event, 'displayClick');" @@ -463,16 +463,20 @@ "name": "{i18n:scada.symbol.units}", "type": "units", "default": "m³/hr", - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", "fieldClass": "medium-width", - "min": null, - "max": null, - "step": null + "disabled": false, + "visible": true + }, + { + "id": "valueDecimals", + "name": "{i18n:scada.symbol.decimals}", + "type": "number", + "default": 0, + "fieldClass": "medium-width", + "min": 0, + "step": 1, + "disabled": false, + "visible": true }, { "id": "defaultBorderColor", @@ -511,16 +515,8 @@ }, "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';" }, - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + "disabled": false, + "visible": true }, { "id": "activeBorderColor", @@ -559,16 +555,8 @@ }, "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';" }, - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + "disabled": false, + "visible": true }, { "id": "warningBorderColor", @@ -607,16 +595,8 @@ }, "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';" }, - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + "disabled": false, + "visible": true }, { "id": "criticalBorderColor", @@ -655,16 +635,8 @@ }, "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';" }, - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + "disabled": false, + "visible": true }, { "id": "backgroundColor", @@ -703,48 +675,24 @@ }, "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';" }, - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + "disabled": false, + "visible": true }, { "id": "fluidColor", "name": "{i18n:scada.symbol.fluid-color}", "type": "color", "default": "#1EC1F480", - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + "disabled": false, + "visible": true }, { "id": "pipeColor", "name": "{i18n:scada.symbol.pipe-color}", "type": "color", "default": "#FFFFFF", - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + "disabled": false, + "visible": true } ] }]]> diff --git a/application/src/main/data/json/system/scada_symbols/bottom-light-bulb-hp.svg b/application/src/main/data/json/system/scada_symbols/bottom-light-bulb-hp.svg new file mode 100644 index 0000000000..4f0603b904 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/bottom-light-bulb-hp.svg @@ -0,0 +1,346 @@ +{ + "title": "HP Bottom light bulb", + "description": "Bottom light bulb with various states.", + "searchTags": [ + "energy" + ], + "widgetSizeX": 1, + "widgetSizeY": 1, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "required": null, + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "required": null, + "subLabel": "{i18n:scada.symbol.stopped}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "required": null, + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "required": null, + "subLabel": "{i18n:scada.symbol.critical}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + } + ] +} + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/consumers-hp.svg b/application/src/main/data/json/system/scada_symbols/consumers-hp.svg new file mode 100644 index 0000000000..e4005d7ae2 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/consumers-hp.svg @@ -0,0 +1,346 @@ +{ + "title": "HP Consumers", + "description": "Consumers with various states.", + "searchTags": [ + "power", + "energy" + ], + "widgetSizeX": 3, + "widgetSizeY": 2, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "required": null, + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "required": null, + "subLabel": "{i18n:scada.symbol.stopped}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "required": null, + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "required": null, + "subLabel": "{i18n:scada.symbol.critical}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + } + ] +} + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/control-panel-hp.svg b/application/src/main/data/json/system/scada_symbols/control-panel-hp.svg index 630d0faa28..d9b0857156 100644 --- a/application/src/main/data/json/system/scada_symbols/control-panel-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/control-panel-hp.svg @@ -1,6 +1,6 @@ { "title": "HP Control panel", - "description": "Control panel", + "description": "Sends the command to the device or updates attribute/time series when the user pushes the button. Widget settings will enable you to configure behavior, how to fetch the initial state, and what to trigger when power on/off states.", "searchTags": [ "control" ], diff --git a/application/src/main/data/json/system/scada_symbols/curcuit-breaker-hp.svg b/application/src/main/data/json/system/scada_symbols/curcuit-breaker-hp.svg new file mode 100644 index 0000000000..12f8b4944f --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/curcuit-breaker-hp.svg @@ -0,0 +1,444 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="400" fill="none" version="1.1" viewBox="0 0 200 400"><tb:metadata xmlns=""><![CDATA[{ + "title": "HP Circuit breaker", + "description": "Circuit breaker with various states.", + "searchTags": [ + "energy", + "power", + "ev", + "switch" + ], + "widgetSizeX": 1, + "widgetSizeY": 2, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "element.attr({fill: ctx.properties.backgroundColor});", + "actions": null + }, + { + "tag": "breaker", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "var initial = ctx.values.initialState;\nvar action = initial ? 'offUpdateState' : 'onUpdateState';\n\nctx.api.callAction(event, action, undefined, {\n next: () => {\n ctx.api.setValue('initialState', !initial);\n }\n});" + } + } + }, + { + "tag": "breaker-trigger", + "stateRenderFunction": "element.fill(ctx.properties.disabledColor);\nif (ctx.values.initialState) {\n element.transform({translateY: 0});\n} else {\n element.transform({translateY: 160});\n}", + "actions": null + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "label", + "stateRenderFunction": "if (ctx.properties.label) {\n element.show();\n var label = ctx.values.initialState ? ctx.properties.onLabel : ctx.properties.offLabel;\n ctx.api.font(element, ctx.properties.labelFont, ctx.properties.labelColor);\n ctx.api.text(element, label);\n} else {\n element.hide();\n}", + "actions": null + }, + { + "tag": "label-box", + "stateRenderFunction": "var color = ctx.properties.disabledColor;\nif (ctx.values.initialState) {\n color = ctx.properties.enabledColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "initialState", + "name": "{i18n:scada.symbol.on-off-state}", + "hint": "{i18n:scada.symbol.on-off-state-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": "{i18n:scada.symbol.on}", + "falseLabel": "{i18n:scada.symbol.off}", + "stateLabel": "", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "onUpdateState", + "name": "{i18n:scada.symbol.on-update-state}", + "hint": "{i18n:scada.symbol.on-update-state-hint}", + "group": null, + "type": "action", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": null, + "defaultSetValueSettings": { + "action": "EXECUTE_RPC", + "executeRpc": { + "method": "setState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "setAttribute": { + "scope": "SERVER_SCOPE", + "key": "state" + }, + "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": "offUpdateState", + "name": "{i18n:scada.symbol.off-update-state}", + "hint": "{i18n:scada.symbol.off-update-state-hint}", + "group": null, + "type": "action", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": null, + "defaultSetValueSettings": { + "action": "EXECUTE_RPC", + "executeRpc": { + "method": "setState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "setAttribute": { + "key": "state", + "scope": "SERVER_SCOPE" + }, + "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}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + } + ], + "properties": [ + { + "id": "label", + "name": "{i18n:scada.symbol.label}", + "type": "switch", + "default": true, + "disabled": false, + "visible": true + }, + { + "id": "onLabel", + "name": "{i18n:scada.symbol.label}", + "type": "text", + "default": "ON", + "disabled": false, + "visible": true + }, + { + "id": "offLabel", + "name": "{i18n:scada.symbol.label}", + "type": "text", + "default": "OFF", + "disabled": false, + "visible": true + }, + { + "id": "labelFont", + "name": "{i18n:scada.symbol.label}", + "type": "font", + "default": { + "size": 42, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "labelColor", + "name": "{i18n:scada.symbol.label}", + "type": "color", + "default": "#1A1A1A", + "disabled": false, + "visible": true + }, + { + "id": "backgroundColor", + "name": "{i18n:scada.symbol.background-color}", + "type": "color", + "default": "#FFFFFF", + "disabled": false, + "visible": true + }, + { + "id": "enabledColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "subLabel": "{i18n:scada.symbol.enabled}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "disabledColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "subLabel": "{i18n:scada.symbol.disabled}", + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "subLabel": "{i18n:scada.symbol.critical}", + "disabled": false, + "visible": true + } + ] +} + + + + ON + + + + + + + + + + + + + + + + \ 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 98185cbc8d..960abed340 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 @@ -776,7 +776,7 @@ } ] }]]> -Outdoor°C +Outdoor°C diff --git a/application/src/main/data/json/system/scada_symbols/electrical-distribution-board-hp.svg b/application/src/main/data/json/system/scada_symbols/electrical-distribution-board-hp.svg new file mode 100644 index 0000000000..0fcfe4dde0 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/electrical-distribution-board-hp.svg @@ -0,0 +1,332 @@ +{ + "title": "HP Electrical distribution board", + "description": "Electrical distribution board with various states.", + "searchTags": [ + "energy", + "power", + "fuse", + "panel" + ], + "widgetSizeX": 2, + "widgetSizeY": 3, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "element.attr({fill: ctx.properties.backgroundColor});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "icon", + "stateRenderFunction": "var showIcon = ctx.properties.showIcon;\nvar showLabel = ctx.properties.label;\nif (showIcon) {\n element.show();\n var icon = ctx.properties.icon;\n var iconSize = ctx.properties.iconSize;\n var iconColor = ctx.properties.iconColor;\n ctx.api.icon(element, icon, iconSize, iconColor, true);\n if (!showLabel) {\n element.transform({translateX: 200, translateY: 310});\n }\n} else {\n element.hide()\n}", + "actions": null + }, + { + "tag": "triangle", + "stateRenderFunction": "if (ctx.properties.showTriangle) {\n element.show();\n element.stroke(ctx.properties.triangleColor);\n} else {\n element.hide();\n}", + "actions": null + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "showIcon", + "name": "{i18n:scada.symbol.icon}", + "type": "switch", + "default": true, + "disabled": false, + "visible": true + }, + { + "id": "iconSize", + "name": "{i18n:scada.symbol.icon}", + "type": "number", + "default": 44, + "fieldSuffix": "px", + "min": 0, + "disabled": false, + "visible": true + }, + { + "id": "icon", + "name": "{i18n:scada.symbol.icon}", + "type": "icon", + "default": "bolt", + "disabled": false, + "visible": true + }, + { + "id": "showTriangle", + "name": "{i18n:scada.symbol.triangle}", + "type": "switch", + "default": true, + "disabled": false, + "visible": true + }, + { + "id": "triangleColor", + "name": "{i18n:scada.symbol.triangle}", + "type": "color", + "default": "#D12730", + "disabled": false, + "visible": true + }, + { + "id": "iconColor", + "name": "{i18n:scada.symbol.icon}", + "type": "color", + "default": "#000", + "disabled": false, + "visible": true + }, + { + "id": "backgroundColor", + "name": "{i18n:scada.symbol.background-color}", + "type": "color", + "default": "#FFFFFF", + "divider": false, + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "subLabel": "{i18n:scada.symbol.critical}", + "disabled": false, + "visible": true + } + ] +} + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/electrical-engine-hp.svg b/application/src/main/data/json/system/scada_symbols/electrical-engine-hp.svg new file mode 100644 index 0000000000..a41de55142 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/electrical-engine-hp.svg @@ -0,0 +1,360 @@ +{ + "title": "HP Electrical engine", + "description": "Electrical engine with various states.", + "searchTags": [ + "extraction", + "power" + ], + "widgetSizeX": 3, + "widgetSizeY": 2, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "required": null, + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "required": null, + "subLabel": "{i18n:scada.symbol.stopped}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "required": null, + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "required": null, + "subLabel": "{i18n:scada.symbol.critical}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + } + ] +} + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/energy-meter-hp.svg b/application/src/main/data/json/system/scada_symbols/energy-meter-hp.svg new file mode 100644 index 0000000000..606eb1153d --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/energy-meter-hp.svg @@ -0,0 +1,482 @@ +{ + "title": "HP Energy meter", + "description": "Energy meter with various states.", + "searchTags": [ + "power", + "energy" + ], + "widgetSizeX": 2, + "widgetSizeY": 2, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "label", + "stateRenderFunction": "if (ctx.properties.showLabel) {\n element.show();\n ctx.api.font(element, ctx.properties.labelFont, ctx.properties.labelColor);\n ctx.api.text(element, ctx.properties.label);\n} else {\n element.hide();\n}", + "actions": null + }, + { + "tag": "units", + "stateRenderFunction": "if (ctx.properties.showUnits) {\n element.show();\n ctx.api.font(element, ctx.properties.unitsFont, ctx.properties.unitsColor);\n ctx.api.text(element, ctx.properties.units);\n} else {\n element.hide();\n}", + "actions": null + }, + { + "tag": "value", + "stateRenderFunction": "ctx.api.font(element, ctx.properties.valueFont, ctx.properties.valueColor);\nctx.api.text(element, ctx.api.formatValue(ctx.values.measured, 0, null, 0));", + "actions": null + }, + { + "tag": "value-box", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.valueBoxBackground;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "measured", + "name": "{i18n:scada.symbol.measured}", + "hint": "{i18n:scada.symbol.measured-hint}", + "group": null, + "type": "value", + "valueType": "DOUBLE", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": { + "action": "GET_TIME_SERIES", + "defaultValue": null, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "measured" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "subLabel": "{i18n:scada.symbol.stopped}", + "disabled": false, + "visible": true + }, + { + "id": "showLabel", + "name": "{i18n:scada.symbol.label}", + "type": "switch", + "default": true, + "disabled": false, + "visible": true + }, + { + "id": "label", + "name": "{i18n:scada.symbol.label}", + "type": "text", + "default": "T1", + "fieldClass": "medium-width", + "disabled": false, + "visible": true + }, + { + "id": "labelFont", + "name": "{i18n:scada.symbol.label}", + "type": "font", + "default": { + "size": 36, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "labelColor", + "name": "{i18n:scada.symbol.label}", + "type": "color", + "default": "#000", + "disabled": false, + "visible": true + }, + { + "id": "showUnits", + "name": "{i18n:scada.symbol.units}", + "type": "switch", + "default": true, + "disabled": false, + "visible": true + }, + { + "id": "units", + "name": "{i18n:scada.symbol.units}", + "type": "units", + "default": "kWh", + "disabled": false, + "visible": true + }, + { + "id": "unitsFont", + "name": "{i18n:scada.symbol.units}", + "type": "font", + "default": { + "size": 36, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "unitsColor", + "name": "{i18n:scada.symbol.units}", + "type": "color", + "default": "#000", + "disabled": false, + "visible": true + }, + { + "id": "valueFont", + "name": "{i18n:scada.symbol.value}", + "type": "font", + "default": { + "size": 48, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "valueColor", + "name": "{i18n:scada.symbol.value}", + "type": "color", + "default": "#002878", + "disabled": false, + "visible": true + }, + { + "id": "valueBoxBackground", + "name": "{i18n:scada.symbol.value-box-background}", + "type": "color", + "default": "#DEDEDE", + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "subLabel": "{i18n:scada.symbol.critical}", + "disabled": false, + "visible": true + } + ] +} +000023kWhT1 + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/four-rate-energy-meter-hp.svg b/application/src/main/data/json/system/scada_symbols/four-rate-energy-meter-hp.svg new file mode 100644 index 0000000000..de4fb13836 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/four-rate-energy-meter-hp.svg @@ -0,0 +1,876 @@ +{ + "title": "HP Four-rate energy meter", + "description": "Four-rate energy meter with various states.", + "searchTags": [ + "power", + "energy" + ], + "widgetSizeX": 3, + "widgetSizeY": 2, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "export-label", + "stateRenderFunction": "if (ctx.properties.showExportLabel) {\n element.show();\n ctx.api.font(element, ctx.properties.exportLabelFont, ctx.properties.exportLabelColor);\n ctx.api.text(element, ctx.properties.exportLabel);\n} else {\n element.hide();\n}", + "actions": null + }, + { + "tag": "export-rate", + "stateRenderFunction": "ctx.api.font(element, ctx.properties.exportValueFont, ctx.properties.exportValueColor);\nctx.api.text(element, ctx.api.formatValue(ctx.values.exportRate, 0, null, 0));", + "actions": null + }, + { + "tag": "night-label", + "stateRenderFunction": "if (ctx.properties.showNightLabel) {\n element.show();\n ctx.api.font(element, ctx.properties.nightLabelFont, ctx.properties.nightLabelColor);\n ctx.api.text(element, ctx.properties.nightLabel);\n} else {\n element.hide();\n}", + "actions": null + }, + { + "tag": "night-rate", + "stateRenderFunction": "ctx.api.font(element, ctx.properties.nightValueFont, ctx.properties.nightValueColor);\nctx.api.text(element, ctx.api.formatValue(ctx.values.nightRate, 0, null, 0));", + "actions": null + }, + { + "tag": "off-peak-label", + "stateRenderFunction": "if (ctx.properties.showOffPeakLabel) {\n element.show();\n ctx.api.font(element, ctx.properties.offPeakLabelFont, ctx.properties.offPeakLabelColor);\n ctx.api.text(element, ctx.properties.offPeakLabel);\n} else {\n element.hide();\n}", + "actions": null + }, + { + "tag": "off-peak-rate", + "stateRenderFunction": "ctx.api.font(element, ctx.properties.offPeakValueFont, ctx.properties.offPeakValueColor);\nctx.api.text(element, ctx.api.formatValue(ctx.values.offPeakRate, 0, null, 0));", + "actions": null + }, + { + "tag": "peak-label", + "stateRenderFunction": "if (ctx.properties.showPeakLabel) {\n element.show();\n ctx.api.font(element, ctx.properties.peakLabelFont, ctx.properties.peakLabelColor);\n ctx.api.text(element, ctx.properties.peakLabel);\n} else {\n element.hide();\n}", + "actions": null + }, + { + "tag": "peak-rate", + "stateRenderFunction": "ctx.api.font(element, ctx.properties.peakValueFont, ctx.properties.peakValueColor);\nctx.api.text(element, ctx.api.formatValue(ctx.values.peakRate, 0, null, 0));", + "actions": null + }, + { + "tag": "units", + "stateRenderFunction": "if (ctx.properties.showUnits) {\n element.show();\n ctx.api.font(element, ctx.properties.unitsFont, ctx.properties.unitsColor);\n ctx.api.text(element, ctx.properties.units);\n} else {\n element.hide();\n}", + "actions": null + }, + { + "tag": "value-box-export", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.exportValueBoxBackground;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "value-box-night", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.nightValueBoxBackground;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "value-box-off-peak", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.offPeakValueBoxBackground;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "value-box-peak", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.peakValueBoxBackground;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "offPeakRate", + "name": "{i18n:scada.symbol.off-peak-rate}", + "hint": "{i18n:scada.symbol.measured-hint}", + "group": null, + "type": "value", + "valueType": "DOUBLE", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": { + "action": "GET_TIME_SERIES", + "defaultValue": null, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "offPeakRate" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "nightRate", + "name": "{i18n:scada.symbol.night-rate}", + "hint": "{i18n:scada.symbol.measured-hint}", + "group": null, + "type": "value", + "valueType": "DOUBLE", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": { + "action": "GET_TIME_SERIES", + "defaultValue": null, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "nightRate" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "peakRate", + "name": "{i18n:scada.symbol.peak-rate}", + "hint": "{i18n:scada.symbol.measured-hint}", + "group": null, + "type": "value", + "valueType": "DOUBLE", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": { + "action": "GET_TIME_SERIES", + "defaultValue": null, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "peakRate" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "exportRate", + "name": "{i18n:scada.symbol.export-rate}", + "hint": "{i18n:scada.symbol.measured-hint}", + "group": null, + "type": "value", + "valueType": "DOUBLE", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": { + "action": "GET_TIME_SERIES", + "defaultValue": null, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "exportRate" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "subLabel": "{i18n:scada.symbol.stopped}", + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "subLabel": "{i18n:scada.symbol.critical}", + "disabled": false, + "visible": true + }, + { + "id": "showOffPeakLabel", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.off-peak-rate}", + "type": "switch", + "default": true, + "disabled": false, + "visible": true + }, + { + "id": "offPeakLabel", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.off-peak-rate}", + "type": "text", + "default": "T1", + "fieldClass": "medium-width", + "disabled": false, + "visible": true + }, + { + "id": "offPeakLabelFont", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.off-peak-rate}", + "type": "font", + "default": { + "size": 36, + "sizeUnit": "px", + "family": "Roboto", + "weight": "normal", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "offPeakLabelColor", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.off-peak-rate}", + "type": "color", + "default": "#000000", + "disabled": false, + "visible": true + }, + { + "id": "offPeakValueFont", + "name": "{i18n:scada.symbol.value}", + "group": "{i18n:scada.symbol.off-peak-rate}", + "type": "font", + "default": { + "size": 48, + "sizeUnit": "px", + "family": "Roboto", + "weight": "normal", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "offPeakValueColor", + "name": "{i18n:scada.symbol.value}", + "group": "{i18n:scada.symbol.off-peak-rate}", + "type": "color", + "default": "#002878", + "disabled": false, + "visible": true + }, + { + "id": "offPeakValueBoxBackground", + "name": "{i18n:scada.symbol.value-box-background}", + "group": "{i18n:scada.symbol.off-peak-rate}", + "type": "color", + "default": "#DEDEDE", + "disabled": false, + "visible": true + }, + { + "id": "showNightLabel", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.night-rate}", + "type": "switch", + "default": true, + "disabled": false, + "visible": true + }, + { + "id": "nightLabel", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.night-rate}", + "type": "text", + "default": "T2", + "fieldClass": "medium-width", + "disabled": false, + "visible": true + }, + { + "id": "nightLabelFont", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.night-rate}", + "type": "font", + "default": { + "size": 36, + "sizeUnit": "px", + "family": "Roboto", + "weight": "normal", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "nightLabelColor", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.night-rate}", + "type": "color", + "default": "#000", + "disabled": false, + "visible": true + }, + { + "id": "nightValueFont", + "name": "{i18n:scada.symbol.value}", + "group": "{i18n:scada.symbol.night-rate}", + "type": "font", + "default": { + "size": 48, + "sizeUnit": "px", + "family": "Roboto", + "weight": "normal", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "nightValueColor", + "name": "{i18n:scada.symbol.value}", + "group": "{i18n:scada.symbol.night-rate}", + "type": "color", + "default": "#002878", + "disabled": false, + "visible": true + }, + { + "id": "nightValueBoxBackground", + "name": "{i18n:scada.symbol.value-box-background}", + "group": "{i18n:scada.symbol.night-rate}", + "type": "color", + "default": "#DEDEDE", + "disabled": false, + "visible": true + }, + { + "id": "showPeakLabel", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.peak-rate}", + "type": "switch", + "default": true, + "disabled": false, + "visible": true + }, + { + "id": "peakLabel", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.peak-rate}", + "type": "text", + "default": "T3", + "fieldClass": "medium-width", + "disabled": false, + "visible": true + }, + { + "id": "peakLabelFont", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.peak-rate}", + "type": "font", + "default": { + "size": 36, + "sizeUnit": "px", + "family": "Roboto", + "weight": "normal", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "peakLabelColor", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.peak-rate}", + "type": "color", + "default": "#000", + "disabled": false, + "visible": true + }, + { + "id": "peakValueFont", + "name": "{i18n:scada.symbol.value}", + "group": "{i18n:scada.symbol.peak-rate}", + "type": "font", + "default": { + "size": 48, + "sizeUnit": "px", + "family": "Roboto", + "weight": "normal", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "peakValueColor", + "name": "{i18n:scada.symbol.value}", + "group": "{i18n:scada.symbol.peak-rate}", + "type": "color", + "default": "#002878", + "disabled": false, + "visible": true + }, + { + "id": "peakValueBoxBackground", + "name": "{i18n:scada.symbol.value-box-background}", + "group": "{i18n:scada.symbol.peak-rate}", + "type": "color", + "default": "#DEDEDE", + "disabled": false, + "visible": true + }, + { + "id": "showExportLabel", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.export-rate}", + "type": "switch", + "default": true, + "disabled": false, + "visible": true + }, + { + "id": "exportLabel", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.export-rate}", + "type": "text", + "default": "Export", + "fieldClass": "medium-width", + "disabled": false, + "visible": true + }, + { + "id": "exportLabelFont", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.export-rate}", + "type": "font", + "default": { + "size": 36, + "sizeUnit": "px", + "family": "Roboto", + "weight": "normal", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "exportLabelColor", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.export-rate}", + "type": "color", + "default": "#000", + "disabled": false, + "visible": true + }, + { + "id": "exportValueFont", + "name": "{i18n:scada.symbol.value}", + "group": "{i18n:scada.symbol.export-rate}", + "type": "font", + "default": { + "size": 48, + "sizeUnit": "px", + "family": "Roboto", + "weight": "normal", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "exportValueColor", + "name": "{i18n:scada.symbol.value}", + "group": "{i18n:scada.symbol.export-rate}", + "type": "color", + "default": "#002878", + "disabled": false, + "visible": true + }, + { + "id": "exportValueBoxBackground", + "name": "{i18n:scada.symbol.value-box-background}", + "group": "{i18n:scada.symbol.export-rate}", + "type": "color", + "default": "#DEDEDE", + "disabled": false, + "visible": true + }, + { + "id": "showUnits", + "name": "{i18n:scada.symbol.units}", + "type": "switch", + "default": true, + "disabled": false, + "visible": true + }, + { + "id": "units", + "name": "{i18n:scada.symbol.units}", + "type": "units", + "default": "kWh", + "disabled": false, + "visible": true + }, + { + "id": "unitsFont", + "name": "{i18n:scada.symbol.units}", + "type": "font", + "default": { + "size": 36, + "sizeUnit": "px", + "family": "Roboto", + "weight": "normal", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "unitsColor", + "name": "{i18n:scada.symbol.units}", + "type": "color", + "default": "#000", + "disabled": false, + "visible": true + } + ] +} +T1T2T3Export000223000223000223000223kWh + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/fuel-generator-hp.svg b/application/src/main/data/json/system/scada_symbols/fuel-generator-hp.svg new file mode 100644 index 0000000000..52a3c70d6e --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/fuel-generator-hp.svg @@ -0,0 +1,359 @@ +{ + "title": "HP Fuel generator", + "description": "Fuel generator with various states.", + "searchTags": [ + "power", + "energy", + "fuel", + "generation" + ], + "widgetSizeX": 1, + "widgetSizeY": 1, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "required": null, + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "required": null, + "subLabel": "{i18n:scada.symbol.stopped}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "required": null, + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "required": null, + "subLabel": "{i18n:scada.symbol.critical}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + } + ] +} + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/gas-preventer-hp.svg b/application/src/main/data/json/system/scada_symbols/gas-preventer-hp.svg new file mode 100644 index 0000000000..ea5a479eda --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/gas-preventer-hp.svg @@ -0,0 +1,361 @@ +{ + "title": "HP Gas preventer", + "description": "Gas preventer with various states.", + "searchTags": [ + "extraction" + ], + "widgetSizeX": 1, + "widgetSizeY": 1, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "required": null, + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "required": null, + "subLabel": "{i18n:scada.symbol.stopped}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "required": null, + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "required": null, + "subLabel": "{i18n:scada.symbol.critical}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + } + ] +} + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/gas-wellhead-hp.svg b/application/src/main/data/json/system/scada_symbols/gas-wellhead-hp.svg new file mode 100644 index 0000000000..c2fee0f7ac --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/gas-wellhead-hp.svg @@ -0,0 +1,363 @@ +{ + "title": "HP Gas wellhead", + "description": "Gas wellhead with various states.", + "searchTags": [ + "extraction" + ], + "widgetSizeX": 4, + "widgetSizeY": 5, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "required": null, + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "required": null, + "subLabel": "{i18n:scada.symbol.stopped}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "required": null, + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "required": null, + "subLabel": "{i18n:scada.symbol.critical}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + } + ] +} + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/heat-exchanger-hp.svg b/application/src/main/data/json/system/scada_symbols/heat-exchanger-hp.svg new file mode 100644 index 0000000000..797220ea96 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/heat-exchanger-hp.svg @@ -0,0 +1,363 @@ +{ + "title": "HP Heat exchanger", + "description": "Heat exchanger with various states.", + "searchTags": [ + "extraction" + ], + "widgetSizeX": 3, + "widgetSizeY": 3, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "required": null, + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "required": null, + "subLabel": "{i18n:scada.symbol.stopped}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "required": null, + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "required": null, + "subLabel": "{i18n:scada.symbol.critical}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + } + ] +} + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/high-voltage-tower-hp.svg b/application/src/main/data/json/system/scada_symbols/high-voltage-tower-hp.svg new file mode 100644 index 0000000000..3aef3ff948 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/high-voltage-tower-hp.svg @@ -0,0 +1,297 @@ +{ + "title": "HP High voltage tower", + "description": "High voltage tower with various states.", + "searchTags": [ + "power", + "energy" + ], + "widgetSizeX": 2, + "widgetSizeY": 4, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "element.attr({fill: ctx.properties.backgroundColor});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "backgroundColor", + "name": "{i18n:scada.symbol.background-color}", + "type": "color", + "default": "#999999", + "divider": false + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "subLabel": "{i18n:scada.symbol.critical}", + "disabled": false, + "visible": true + } + ] +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/horizontal-connector-hp.svg b/application/src/main/data/json/system/scada_symbols/horizontal-connector-hp.svg index 3548d687c5..33b6c0a222 100644 --- a/application/src/main/data/json/system/scada_symbols/horizontal-connector-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/horizontal-connector-hp.svg @@ -1,6 +1,6 @@ { "title": "HP Horizontal connector", - "description": "Horizontal connector", + "description": "Horizontal connector with an optional directional arrow to visually indicate flow.", "widgetSizeX": 1, "widgetSizeY": 1, "tags": [ diff --git a/application/src/main/data/json/system/scada_symbols/horizontal-curcuit-breaker-hp.svg b/application/src/main/data/json/system/scada_symbols/horizontal-curcuit-breaker-hp.svg new file mode 100644 index 0000000000..17fd1ba69e --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/horizontal-curcuit-breaker-hp.svg @@ -0,0 +1,439 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="200" fill="none" version="1.1" viewBox="0 0 400 200"><tb:metadata xmlns=""><![CDATA[{ + "title": "HP Horizontal circuit breaker", + "description": "Horizontal circuit breaker with various states.", + "searchTags": [ + "energy", + "power", + "ev" + ], + "widgetSizeX": 2, + "widgetSizeY": 1, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "element.attr({fill: ctx.properties.backgroundColor});", + "actions": null + }, + { + "tag": "breaker", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "var initial = ctx.values.initialState;\nvar action = initial ? 'offUpdateState' : 'onUpdateState';\n\nctx.api.callAction(event, action, undefined, {\n next: () => {\n ctx.api.setValue('initialState', !initial);\n }\n});" + } + } + }, + { + "tag": "breaker-trigger", + "stateRenderFunction": "element.fill(ctx.properties.disabledColor);\nif (ctx.values.initialState) {\n element.transform({translateX: 0});\n} else {\n element.transform({translateX: -160});\n}", + "actions": null + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "label", + "stateRenderFunction": "if (ctx.properties.label) {\n element.show();\n var label = ctx.values.initialState ? ctx.properties.onLabel : ctx.properties.offLabel;\n ctx.api.font(element, ctx.properties.labelFont, ctx.properties.labelColor);\n ctx.api.text(element, label);\n} else {\n element.hide();\n}", + "actions": null + }, + { + "tag": "label-box", + "stateRenderFunction": "var color = ctx.properties.disabledColor;\nif (ctx.values.initialState) {\n color = ctx.properties.enabledColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "initialState", + "name": "{i18n:scada.symbol.on-off-state}", + "hint": "{i18n:scada.symbol.on-off-state-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": "{i18n:scada.symbol.on}", + "falseLabel": "{i18n:scada.symbol.off}", + "stateLabel": "", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "onUpdateState", + "name": "{i18n:scada.symbol.on-update-state}", + "hint": "{i18n:scada.symbol.on-update-state-hint}", + "group": null, + "type": "action", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": null, + "defaultSetValueSettings": { + "action": "EXECUTE_RPC", + "executeRpc": { + "method": "setState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "setAttribute": { + "scope": "SERVER_SCOPE", + "key": "state" + }, + "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": "offUpdateState", + "name": "{i18n:scada.symbol.off-update-state}", + "hint": "{i18n:scada.symbol.off-update-state-hint}", + "group": null, + "type": "action", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": null, + "defaultSetValueSettings": { + "action": "EXECUTE_RPC", + "executeRpc": { + "method": "setState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "setAttribute": { + "key": "state", + "scope": "SERVER_SCOPE" + }, + "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}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + } + ], + "properties": [ + { + "id": "label", + "name": "{i18n:scada.symbol.label}", + "type": "switch", + "default": true, + "disabled": false, + "visible": true + }, + { + "id": "onLabel", + "name": "{i18n:scada.symbol.label}", + "type": "text", + "default": "ON", + "disabled": false, + "visible": true + }, + { + "id": "offLabel", + "name": "{i18n:scada.symbol.label}", + "type": "text", + "default": "OFF", + "disabled": false, + "visible": true + }, + { + "id": "labelFont", + "name": "{i18n:scada.symbol.label}", + "type": "font", + "default": { + "size": 42, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "labelColor", + "name": "{i18n:scada.symbol.label}", + "type": "color", + "default": "#1A1A1A", + "disabled": false, + "visible": true + }, + { + "id": "backgroundColor", + "name": "{i18n:scada.symbol.background-color}", + "type": "color", + "default": "#FFFFFF", + "disabled": false, + "visible": true + }, + { + "id": "enabledColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "subLabel": "{i18n:scada.symbol.enabled}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "disabledColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "subLabel": "{i18n:scada.symbol.disabled}", + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "subLabel": "{i18n:scada.symbol.critical}", + "disabled": false, + "visible": true + } + ] +} + + + ON + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/horizontal-energy-system-controller-hp.svg b/application/src/main/data/json/system/scada_symbols/horizontal-energy-system-controller-hp.svg new file mode 100644 index 0000000000..13ee85821b --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/horizontal-energy-system-controller-hp.svg @@ -0,0 +1,378 @@ +{ + "title": "HP Horizontal energy systems controller", + "description": "Horizontal energy systems controller with various states.", + "searchTags": [ + "energy", + "power", + "monitoring" + ], + "widgetSizeX": 3, + "widgetSizeY": 2, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.disconnectedColor;\nif (ctx.values.connected) {\n color = ctx.properties.connectedColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "indicator", + "stateRenderFunction": "var color = ctx.properties.disconnectedIndicatorColor;\nif (ctx.values.connected) {\n color = ctx.properties.connectedIndicatorColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "label", + "stateRenderFunction": "ctx.api.font(element, ctx.properties.labelFont, ctx.properties.labelColor);\n ctx.api.text(element, ctx.properties.label);", + "actions": null + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "connected", + "name": "{i18n:scada.symbol.connected}", + "hint": "{i18n:scada.symbol.connected-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.connected}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "connected" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "connectedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "subLabel": "{i18n:scada.symbol.connected}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "disconnectedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "subLabel": "{i18n:scada.symbol.disconnected}", + "disabled": false, + "visible": true + }, + { + "id": "connectedIndicatorColor", + "name": "{i18n:scada.symbol.indicator}", + "type": "color", + "default": "#198038", + "subLabel": "{i18n:scada.symbol.connected}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "disconnectedIndicatorColor", + "name": "{i18n:scada.symbol.indicator}", + "type": "color", + "default": "#DEDEDE", + "subLabel": "{i18n:scada.symbol.disconnected}", + "disabled": false, + "visible": true + }, + { + "id": "label", + "name": "{i18n:scada.symbol.label}", + "type": "text", + "default": "Connected", + "fieldClass": "medium-width", + "disabled": false, + "visible": true + }, + { + "id": "labelFont", + "name": "{i18n:scada.symbol.label}", + "type": "font", + "default": { + "size": 30, + "sizeUnit": "px", + "family": "Roboto", + "weight": "normal", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "labelColor", + "name": "{i18n:scada.symbol.label}", + "type": "color", + "default": "#000", + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "subLabel": "{i18n:scada.symbol.critical}", + "disabled": false, + "visible": true + } + ] +} +Connected + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/horizontal-inline-flow-meter.svg b/application/src/main/data/json/system/scada_symbols/horizontal-inline-flow-meter.svg index 4ad766764a..5e0c781941 100644 --- a/application/src/main/data/json/system/scada_symbols/horizontal-inline-flow-meter.svg +++ b/application/src/main/data/json/system/scada_symbols/horizontal-inline-flow-meter.svg @@ -57,7 +57,7 @@ }, { "tag": "value", - "stateRenderFunction": "var value = ctx.values.value;\nctx.api.text(element, value.toFixed(0));\n", + "stateRenderFunction": "var value = ctx.api.formatValue(ctx.values.value, ctx.properties.valueDecimals, '', false);\nctx.api.text(element, value);\n", "actions": { "click": { "actionFunction": "ctx.api.callAction(event, 'displayClick');" @@ -463,16 +463,20 @@ "name": "{i18n:scada.symbol.units}", "type": "units", "default": "m³/hr", - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", "fieldClass": "medium-width", - "min": null, - "max": null, - "step": null + "disabled": false, + "visible": true + }, + { + "id": "valueDecimals", + "name": "{i18n:scada.symbol.decimals}", + "type": "number", + "default": 0, + "fieldClass": "medium-width", + "min": 0, + "step": 1, + "disabled": false, + "visible": true }, { "id": "defaultBorderColor", @@ -511,16 +515,8 @@ }, "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';" }, - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + "disabled": false, + "visible": true }, { "id": "activeBorderColor", @@ -559,16 +555,8 @@ }, "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';" }, - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + "disabled": false, + "visible": true }, { "id": "warningBorderColor", @@ -607,16 +595,8 @@ }, "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';" }, - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + "disabled": false, + "visible": true }, { "id": "criticalBorderColor", @@ -655,16 +635,8 @@ }, "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';" }, - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + "disabled": false, + "visible": true }, { "id": "backgroundColor", @@ -703,48 +675,24 @@ }, "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';" }, - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + "disabled": false, + "visible": true }, { "id": "fluidColor", "name": "{i18n:scada.symbol.fluid-color}", "type": "color", "default": "#1EC1F480", - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + "disabled": false, + "visible": true }, { "id": "pipeColor", "name": "{i18n:scada.symbol.pipe-color}", "type": "color", "default": "#FFFFFF", - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + "disabled": false, + "visible": true } ] }]]> diff --git a/application/src/main/data/json/system/scada_symbols/house-hp.svg b/application/src/main/data/json/system/scada_symbols/house-hp.svg new file mode 100644 index 0000000000..d95c47bb8b --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/house-hp.svg @@ -0,0 +1,349 @@ +{ + "title": "HP House", + "description": "House with various states.", + "searchTags": [ + "power", + "energy", + "consumer" + ], + "widgetSizeX": 2, + "widgetSizeY": 2, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "required": null, + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "required": null, + "subLabel": "{i18n:scada.symbol.stopped}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "required": null, + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "required": null, + "subLabel": "{i18n:scada.symbol.critical}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + } + ] +} + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/industrial-fuel-generator-hp.svg b/application/src/main/data/json/system/scada_symbols/industrial-fuel-generator-hp.svg new file mode 100644 index 0000000000..2b7a476a92 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/industrial-fuel-generator-hp.svg @@ -0,0 +1,358 @@ +{ + "title": "HP Industrial fuel generator", + "description": "Industrial fuel generator with various states.", + "searchTags": [ + "power", + "energy", + "fuel", + "generation" + ], + "widgetSizeX": 5, + "widgetSizeY": 3, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "sub-background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = '#999999';\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "required": null, + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "required": null, + "subLabel": "{i18n:scada.symbol.stopped}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "required": null, + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "required": null, + "subLabel": "{i18n:scada.symbol.critical}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + } + ] +} + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/inverter-hp.svg b/application/src/main/data/json/system/scada_symbols/inverter-hp.svg new file mode 100644 index 0000000000..73f14c8e75 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/inverter-hp.svg @@ -0,0 +1,578 @@ +{ + "title": "HP Inverter", + "description": "Inverter with various states.", + "searchTags": [ + "energy", + "power", + "hybrid", + "solar", + "backup" + ], + "widgetSizeX": 3, + "widgetSizeY": 3, + "tags": [ + { + "tag": "absorption", + "stateRenderFunction": "if (ctx.values.running && ctx.values.chargingMode === 2) {\n element.fill(ctx.properties.chargingIndicatorsColor);\n} else {\n element.fill('#dedede');\n}", + "actions": null + }, + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "bulk", + "stateRenderFunction": "if (ctx.values.running && ctx.values.chargingMode === 1) {\n element.fill(ctx.properties.chargingIndicatorsColor);\n} else {\n element.fill('#dedede');\n}", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "float", + "stateRenderFunction": "if (ctx.values.running && ctx.values.chargingMode === 3) {\n element.fill(ctx.properties.chargingIndicatorsColor);\n} else {\n element.fill('#dedede');\n}", + "actions": null + }, + { + "tag": "inverterOn", + "stateRenderFunction": "if (ctx.values.running && !ctx.values.operationMode) {\n element.fill(ctx.properties.operationIndicatorsColor);\n} else {\n element.fill('#dedede');\n}", + "actions": null + }, + { + "tag": "lowBattery", + "stateRenderFunction": "if (ctx.values.running && ctx.values.lowBattery) {\n element.fill(ctx.properties.faultIndicatorsColor);\n} else {\n element.fill('#dedede');\n}", + "actions": null + }, + { + "tag": "mainsOn", + "stateRenderFunction": "if (ctx.values.running && ctx.values.operationMode) {\n element.fill(ctx.properties.operationIndicatorsColor);\n} else {\n element.fill('#dedede');\n}", + "actions": null + }, + { + "tag": "overload", + "stateRenderFunction": "if (ctx.values.running && ctx.values.overload) {\n element.fill(ctx.properties.faultIndicatorsColor);\n} else {\n element.fill('#dedede');\n}", + "actions": null + }, + { + "tag": "temperature", + "stateRenderFunction": "if (ctx.values.running && ctx.values.temperature) {\n element.fill(ctx.properties.faultIndicatorsColor);\n} else {\n element.fill('#dedede');\n}", + "actions": null + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "operationMode", + "name": "{i18n:scada.symbol.operation-mode}", + "hint": "{i18n:scada.symbol.operation-mode-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": "{i18n:scada.symbol.mains-on-mode}", + "falseLabel": "{i18n:scada.symbol.inverter-on-mode}", + "stateLabel": null, + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": true, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "chargingMode", + "name": "{i18n:scada.symbol.charging-mode}", + "hint": "{i18n:scada.symbol.charging-mode-hint}", + "group": null, + "type": "value", + "valueType": "INTEGER", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": null, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "chargingMode" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "overload", + "name": "{i18n:scada.symbol.overload-fault}", + "hint": "{i18n:scada.symbol.overload-fault-hint}", + "group": "{i18n:scada.symbol.inverter-faults}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "lowBattery", + "name": "{i18n:scada.symbol.low-battery-fault}", + "hint": "{i18n:scada.symbol.low-battery-fault-hint}", + "group": "{i18n:scada.symbol.inverter-faults}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "temperature", + "name": "{i18n:scada.symbol.temperature-fault}", + "hint": "{i18n:scada.symbol.temperature-fault-hint}", + "group": "{i18n:scada.symbol.inverter-faults}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "subLabel": "{i18n:scada.symbol.stopped}", + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "subLabel": "{i18n:scada.symbol.critical}", + "disabled": false, + "visible": true + }, + { + "id": "operationIndicatorsColor", + "name": "{i18n:scada.symbol.operation-mode-indicators-color}", + "type": "color", + "default": "#198038", + "disabled": false, + "visible": true + }, + { + "id": "chargingIndicatorsColor", + "name": "{i18n:scada.symbol.charging-mode-indicators-color}", + "type": "color", + "default": "#FAA405", + "disabled": false, + "visible": true + }, + { + "id": "faultIndicatorsColor", + "name": "{i18n:scada.symbol.inverter-fault-indicators-color}", + "type": "color", + "default": "#D12730", + "disabled": false, + "visible": true + } + ] +} + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/large-horizontal-separator-connector-hp.svg b/application/src/main/data/json/system/scada_symbols/large-horizontal-separator-connector-hp.svg new file mode 100644 index 0000000000..ae1c604e9a --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/large-horizontal-separator-connector-hp.svg @@ -0,0 +1,342 @@ +{ + "title": "HP Large horizontal separator with connector", + "description": "Large horizontal separator with connector with various states.", + "searchTags": [ + "extraction" + ], + "widgetSizeX": 5, + "widgetSizeY": 3, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "required": null, + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "required": null, + "subLabel": "{i18n:scada.symbol.stopped}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "required": null, + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "required": null, + "subLabel": "{i18n:scada.symbol.critical}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + } + ] +} + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/large-horizontal-separator-hp.svg b/application/src/main/data/json/system/scada_symbols/large-horizontal-separator-hp.svg new file mode 100644 index 0000000000..79f6745a73 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/large-horizontal-separator-hp.svg @@ -0,0 +1,350 @@ +{ + "title": "HP Large horizontal separator", + "description": "Large horizontal separator with various states.", + "searchTags": [ + "extraction" + ], + "widgetSizeX": 5, + "widgetSizeY": 3, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "required": null, + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "required": null, + "subLabel": "{i18n:scada.symbol.stopped}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "required": null, + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "required": null, + "subLabel": "{i18n:scada.symbol.critical}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + } + ] +} + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/large-inverter-hp.svg b/application/src/main/data/json/system/scada_symbols/large-inverter-hp.svg new file mode 100644 index 0000000000..11bc5da0a6 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/large-inverter-hp.svg @@ -0,0 +1,578 @@ +{ + "title": "HP Large inverter", + "description": "Large inverter with various states.", + "searchTags": [ + "energy", + "power", + "hybrid", + "solar", + "backup" + ], + "widgetSizeX": 6, + "widgetSizeY": 3, + "tags": [ + { + "tag": "absorption", + "stateRenderFunction": "if (ctx.values.running && ctx.values.chargingMode === 2) {\n element.fill(ctx.properties.chargingIndicatorsColor);\n} else {\n element.fill('#dedede');\n}", + "actions": null + }, + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "bulk", + "stateRenderFunction": "if (ctx.values.running && ctx.values.chargingMode === 1) {\n element.fill(ctx.properties.chargingIndicatorsColor);\n} else {\n element.fill('#dedede');\n}", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "float", + "stateRenderFunction": "if (ctx.values.running && ctx.values.chargingMode === 3) {\n element.fill(ctx.properties.chargingIndicatorsColor);\n} else {\n element.fill('#dedede');\n}", + "actions": null + }, + { + "tag": "inverterOn", + "stateRenderFunction": "if (ctx.values.running && !ctx.values.operationMode) {\n element.fill(ctx.properties.operationIndicatorsColor);\n} else {\n element.fill('#dedede');\n}", + "actions": null + }, + { + "tag": "lowBattery", + "stateRenderFunction": "if (ctx.values.running && ctx.values.lowBattery) {\n element.fill(ctx.properties.faultIndicatorsColor);\n} else {\n element.fill('#dedede');\n}", + "actions": null + }, + { + "tag": "mainsOn", + "stateRenderFunction": "if (ctx.values.running && ctx.values.operationMode) {\n element.fill(ctx.properties.operationIndicatorsColor);\n} else {\n element.fill('#dedede');\n}", + "actions": null + }, + { + "tag": "overload", + "stateRenderFunction": "if (ctx.values.running && ctx.values.overload) {\n element.fill(ctx.properties.faultIndicatorsColor);\n} else {\n element.fill('#dedede');\n}", + "actions": null + }, + { + "tag": "temperature", + "stateRenderFunction": "if (ctx.values.running && ctx.values.temperature) {\n element.fill(ctx.properties.faultIndicatorsColor);\n} else {\n element.fill('#dedede');\n}", + "actions": null + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "operationMode", + "name": "{i18n:scada.symbol.operation-mode}", + "hint": "{i18n:scada.symbol.operation-mode-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": "{i18n:scada.symbol.mains-on-mode}", + "falseLabel": "{i18n:scada.symbol.inverter-on-mode}", + "stateLabel": null, + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": true, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "chargingMode", + "name": "{i18n:scada.symbol.charging-mode}", + "hint": "{i18n:scada.symbol.charging-mode-hint}", + "group": null, + "type": "value", + "valueType": "INTEGER", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": null, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "chargingMode" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "overload", + "name": "{i18n:scada.symbol.overload-fault}", + "hint": "{i18n:scada.symbol.overload-fault-hint}", + "group": "{i18n:scada.symbol.inverter-faults}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "lowBattery", + "name": "{i18n:scada.symbol.low-battery-fault}", + "hint": "{i18n:scada.symbol.low-battery-fault-hint}", + "group": "{i18n:scada.symbol.inverter-faults}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "temperature", + "name": "{i18n:scada.symbol.temperature-fault}", + "hint": "{i18n:scada.symbol.temperature-fault-hint}", + "group": "{i18n:scada.symbol.inverter-faults}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "subLabel": "{i18n:scada.symbol.stopped}", + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "subLabel": "{i18n:scada.symbol.critical}", + "disabled": false, + "visible": true + }, + { + "id": "operationIndicatorsColor", + "name": "{i18n:scada.symbol.operation-mode-indicators-color}", + "type": "color", + "default": "#198038", + "disabled": false, + "visible": true + }, + { + "id": "chargingIndicatorsColor", + "name": "{i18n:scada.symbol.charging-mode-indicators-color}", + "type": "color", + "default": "#FAA405", + "disabled": false, + "visible": true + }, + { + "id": "faultIndicatorsColor", + "name": "{i18n:scada.symbol.inverter-fault-indicators-color}", + "type": "color", + "default": "#D12730", + "disabled": false, + "visible": true + } + ] +} + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/large-vertical-separator-connector-hp.svg b/application/src/main/data/json/system/scada_symbols/large-vertical-separator-connector-hp.svg new file mode 100644 index 0000000000..df463b12a4 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/large-vertical-separator-connector-hp.svg @@ -0,0 +1,350 @@ +{ + "title": "HP Large vertical separator with connector", + "description": "Large vertical separator with connector with various states.", + "searchTags": [ + "extraction" + ], + "widgetSizeX": 3, + "widgetSizeY": 5, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "required": null, + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "required": null, + "subLabel": "{i18n:scada.symbol.stopped}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "required": null, + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "required": null, + "subLabel": "{i18n:scada.symbol.critical}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + } + ] +} + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/large-vertical-separator-hp.svg b/application/src/main/data/json/system/scada_symbols/large-vertical-separator-hp.svg new file mode 100644 index 0000000000..99ff420530 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/large-vertical-separator-hp.svg @@ -0,0 +1,350 @@ +{ + "title": "HP Large vertical separator", + "description": "Large vertical separator with various states.", + "searchTags": [ + "extraction" + ], + "widgetSizeX": 3, + "widgetSizeY": 5, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "required": null, + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "required": null, + "subLabel": "{i18n:scada.symbol.stopped}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "required": null, + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "required": null, + "subLabel": "{i18n:scada.symbol.critical}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + } + ] +} + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/leak-sensor.svg b/application/src/main/data/json/system/scada_symbols/leak-sensor.svg index e2859ea90c..0fce8be608 100644 --- a/application/src/main/data/json/system/scada_symbols/leak-sensor.svg +++ b/application/src/main/data/json/system/scada_symbols/leak-sensor.svg @@ -1,6 +1,6 @@ { "title": "Leak sensor", - "description": "Leak sensor", + "description": "Leak sensor for real-time detection and warning of fluid leakage.", "searchTags": [ "leak" ], diff --git a/application/src/main/data/json/system/scada_symbols/left-analog-water-level-meter.svg b/application/src/main/data/json/system/scada_symbols/left-analog-water-level-meter.svg index 18394d3d2c..2cdc9e587a 100644 --- a/application/src/main/data/json/system/scada_symbols/left-analog-water-level-meter.svg +++ b/application/src/main/data/json/system/scada_symbols/left-analog-water-level-meter.svg @@ -1,6 +1,6 @@ <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{ "title": "Left analog water level meter", - "description": "Left analog water level meter", + "description": "Left analog water level meter with real-time display of fluid levels.", "searchTags": [ "water meter" ], diff --git a/application/src/main/data/json/system/scada_symbols/left-drain-pipe.svg b/application/src/main/data/json/system/scada_symbols/left-drain-pipe.svg index 326a263208..cb7bb26821 100644 --- a/application/src/main/data/json/system/scada_symbols/left-drain-pipe.svg +++ b/application/src/main/data/json/system/scada_symbols/left-drain-pipe.svg @@ -1,7 +1,7 @@ <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="200" fill="none" version="1.1" viewBox="0 0 400 200"> <tb:metadata><![CDATA[{ "title": "Left drain pipe", - "description": "Left drain pipe", + "description": "Left drain pipe with configurable fluid and leak visualizations.", "searchTags": [ "pipe", "drain" diff --git a/application/src/main/data/json/system/scada_symbols/left-elbow-drain-pipe.svg b/application/src/main/data/json/system/scada_symbols/left-elbow-drain-pipe.svg index b9a6aa6057..085830c5e4 100644 --- a/application/src/main/data/json/system/scada_symbols/left-elbow-drain-pipe.svg +++ b/application/src/main/data/json/system/scada_symbols/left-elbow-drain-pipe.svg @@ -1,7 +1,7 @@ <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="200" fill="none" version="1.1" viewBox="0 0 400 200"> <tb:metadata xmlns=""><![CDATA[{ "title": "Left elbow drain pipe", - "description": "Left elbow drain pipe", + "description": "Left elbow drain pipe with configurable fluid and leak visualizations.", "searchTags": [ "pipe", "drain" diff --git a/application/src/main/data/json/system/scada_symbols/left-flow-meter.svg b/application/src/main/data/json/system/scada_symbols/left-flow-meter.svg index 12ec888e66..1c29a79479 100644 --- a/application/src/main/data/json/system/scada_symbols/left-flow-meter.svg +++ b/application/src/main/data/json/system/scada_symbols/left-flow-meter.svg @@ -57,7 +57,7 @@ }, { "tag": "value", - "stateRenderFunction": "var value = ctx.values.value;\nctx.api.text(element, value.toFixed(0));\n", + "stateRenderFunction": "var value = ctx.api.formatValue(ctx.values.value, ctx.properties.valueDecimals, '', false);\nctx.api.text(element, value);\n", "actions": { "click": { "actionFunction": "ctx.api.callAction(event, 'displayClick');" @@ -463,16 +463,16 @@ "name": "{i18n:scada.symbol.units}", "type": "units", "default": "m³/hr", - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", + "fieldClass": "medium-width" + }, + { + "id": "valueDecimals", + "name": "{i18n:scada.symbol.decimals}", + "type": "number", + "default": 0, "fieldClass": "medium-width", - "min": null, - "max": null, - "step": null + "min": 0, + "step": 1 }, { "id": "defaultBorderColor", @@ -510,17 +510,7 @@ "rangeAdvanced": [] }, "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';" - }, - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + } }, { "id": "activeBorderColor", @@ -558,17 +548,7 @@ "rangeAdvanced": [] }, "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';" - }, - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + } }, { "id": "warningBorderColor", @@ -606,17 +586,7 @@ "rangeAdvanced": [] }, "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';" - }, - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + } }, { "id": "criticalBorderColor", @@ -654,17 +624,7 @@ "rangeAdvanced": [] }, "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';" - }, - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + } }, { "id": "backgroundColor", @@ -702,49 +662,19 @@ "rangeAdvanced": [] }, "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';" - }, - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + } }, { "id": "fluidColor", "name": "{i18n:scada.symbol.fluid-color}", "type": "color", - "default": "#1EC1F480", - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + "default": "#1EC1F480" }, { "id": "pipeColor", "name": "{i18n:scada.symbol.pipe-color}", "type": "color", - "default": "#FFFFFF", - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + "default": "#FFFFFF" } ] } diff --git a/application/src/main/data/json/system/scada_symbols/long-bottom-filter.svg b/application/src/main/data/json/system/scada_symbols/long-bottom-filter.svg index d9defd33d9..689851529d 100644 --- a/application/src/main/data/json/system/scada_symbols/long-bottom-filter.svg +++ b/application/src/main/data/json/system/scada_symbols/long-bottom-filter.svg @@ -1,6 +1,6 @@ { - "title": "Long bottom filter", + "title": "Long bottom filter with configurable click actions for custom operations, dashboard manipulation, etc.", "description": "Long bottom filter", "searchTags": [ "filter" 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 4376bb2348..86bb07a520 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,7 +1,7 @@ <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", + "description": "Long horizontal connector with an optional directional arrow to visually indicate flow.", "widgetSizeX": 2, "widgetSizeY": 1, "tags": [ diff --git a/application/src/main/data/json/system/scada_symbols/long-top-filter.svg b/application/src/main/data/json/system/scada_symbols/long-top-filter.svg index a63b679f0a..9b287cbe46 100644 --- a/application/src/main/data/json/system/scada_symbols/long-top-filter.svg +++ b/application/src/main/data/json/system/scada_symbols/long-top-filter.svg @@ -1,6 +1,6 @@ <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="600" fill="none" version="1.1" viewBox="0 0 200 600"> <tb:metadata xmlns=""><![CDATA[{ - "title": "Long top filter", + "title": "Long top filter with configurable click actions for custom operations, dashboard manipulation, etc.", "description": "Title\nLong top filter\n", "searchTags": [ "filter" diff --git a/application/src/main/data/json/system/scada_symbols/long-vertical-connector-hp.svg b/application/src/main/data/json/system/scada_symbols/long-vertical-connector-hp.svg index 86235ff1c1..d7a0aa8aa7 100644 --- a/application/src/main/data/json/system/scada_symbols/long-vertical-connector-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/long-vertical-connector-hp.svg @@ -1,6 +1,6 @@ <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="400" fill="none" version="1.1" viewBox="0 0 200 400"><tb:metadata xmlns=""><![CDATA[{ "title": "HP Long vertical connector", - "description": "Long vertical connector", + "description": "Long vertical connector with an optional directional arrow to visually indicate flow.", "widgetSizeX": 1, "widgetSizeY": 2, "tags": [ diff --git a/application/src/main/data/json/system/scada_symbols/low-voltage-tower-hp.svg b/application/src/main/data/json/system/scada_symbols/low-voltage-tower-hp.svg new file mode 100644 index 0000000000..812e616433 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/low-voltage-tower-hp.svg @@ -0,0 +1,278 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="800" fill="none" version="1.1" viewBox="0 0 400 800"><tb:metadata xmlns=""><![CDATA[{ + "title": "HP Low voltage tower", + "description": "Low voltage tower with various states.", + "searchTags": [ + "power", + "energy" + ], + "widgetSizeX": 2, + "widgetSizeY": 4, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "element.attr({fill: ctx.properties.backgroundColor});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "backgroundColor", + "name": "{i18n:scada.symbol.background-color}", + "type": "color", + "default": "#999999", + "divider": false, + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "subLabel": "{i18n:scada.symbol.critical}", + "disabled": false, + "visible": true + } + ] +} + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/low-voltage-transformer-tower-hp.svg b/application/src/main/data/json/system/scada_symbols/low-voltage-transformer-tower-hp.svg new file mode 100644 index 0000000000..2985dc600c --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/low-voltage-transformer-tower-hp.svg @@ -0,0 +1,334 @@ +{ + "title": "HP Low voltage transformer tower", + "description": "Low voltage transformer tower with various states.", + "searchTags": [ + "power", + "energy" + ], + "widgetSizeX": 2, + "widgetSizeY": 4, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "element.attr({fill: ctx.properties.backgroundColor});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "transformer", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "backgroundColor", + "name": "{i18n:scada.symbol.background-color}", + "type": "color", + "default": "#999999" + }, + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "subLabel": "{i18n:scada.symbol.running}", + "divider": true + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "subLabel": "{i18n:scada.symbol.stopped}" + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "subLabel": "{i18n:scada.symbol.critical}" + } + ] +} + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/manufacture-hp.svg b/application/src/main/data/json/system/scada_symbols/manufacture-hp.svg new file mode 100644 index 0000000000..5b35f2eaf2 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/manufacture-hp.svg @@ -0,0 +1,344 @@ +{ + "title": "HP Manufacture", + "description": "Manufacture with various states.", + "searchTags": [ + "power", + "energy", + "consumer" + ], + "widgetSizeX": 2, + "widgetSizeY": 2, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "required": null, + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "required": null, + "subLabel": "{i18n:scada.symbol.stopped}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "required": null, + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "required": null, + "subLabel": "{i18n:scada.symbol.critical}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + } + ] +} + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/oil-pump-hp.svg b/application/src/main/data/json/system/scada_symbols/oil-pump-hp.svg new file mode 100644 index 0000000000..e7dfa8f0c8 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/oil-pump-hp.svg @@ -0,0 +1,357 @@ +{ + "title": "HP Oil pump", + "description": "Oil pump with various states.", + "searchTags": [ + "extraction" + ], + "widgetSizeX": 5, + "widgetSizeY": 6, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "required": null, + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "required": null, + "subLabel": "{i18n:scada.symbol.stopped}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "required": null, + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "required": null, + "subLabel": "{i18n:scada.symbol.critical}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + } + ] +} + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/power-socket-hp.svg b/application/src/main/data/json/system/scada_symbols/power-socket-hp.svg new file mode 100644 index 0000000000..5524b3bb14 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/power-socket-hp.svg @@ -0,0 +1,371 @@ +{ + "title": "HP Power socket", + "description": "Power socket with various states.", + "searchTags": [ + "EU socket", + "US socket", + "Middle East socket", + "energy" + ], + "widgetSizeX": 1, + "widgetSizeY": 1, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "eu-socket", + "stateRenderFunction": "if (ctx.properties.socketType === 'eu') {\n element.show();\n} else {\n element.hide();\n}", + "actions": null + }, + { + "tag": "middle-east-socket", + "stateRenderFunction": "if (ctx.properties.socketType === 'middleEast') {\n element.show();\n} else {\n element.hide();\n}", + "actions": null + }, + { + "tag": "us-socket", + "stateRenderFunction": "if (ctx.properties.socketType === 'us') {\n element.show();\n} else {\n element.hide();\n}", + "actions": null + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "socketType", + "name": "{i18n:scada.symbol.socket}", + "type": "select", + "default": "eu", + "fieldClass": "flex", + "items": [ + { + "value": "eu", + "label": "EU" + }, + { + "value": "us", + "label": "US" + }, + { + "value": "middleEast", + "label": "Middle East" + } + ], + "disabled": false, + "visible": true + }, + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "subLabel": "{i18n:scada.symbol.stopped}", + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "subLabel": "{i18n:scada.symbol.critical}", + "disabled": false, + "visible": true + } + ] +} + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/power-transformer-hp.svg b/application/src/main/data/json/system/scada_symbols/power-transformer-hp.svg new file mode 100644 index 0000000000..67e75be827 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/power-transformer-hp.svg @@ -0,0 +1,359 @@ +{ + "title": "HP Power transformer", + "description": "Power transformer with various states.", + "searchTags": [ + "power", + "energy" + ], + "widgetSizeX": 2, + "widgetSizeY": 3, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "required": null, + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "required": null, + "subLabel": "{i18n:scada.symbol.stopped}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "required": null, + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "required": null, + "subLabel": "{i18n:scada.symbol.critical}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + } + ] +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/right-analog-water-level-meter.svg b/application/src/main/data/json/system/scada_symbols/right-analog-water-level-meter.svg index 4bd6754f5c..1b1be4f007 100644 --- a/application/src/main/data/json/system/scada_symbols/right-analog-water-level-meter.svg +++ b/application/src/main/data/json/system/scada_symbols/right-analog-water-level-meter.svg @@ -1,6 +1,6 @@ { "title": "Right analog water level meter", - "description": "Right analog water level meter", + "description": "Right analog water level meter with real-time display of fluid levels.", "searchTags": [ "water meter" ], diff --git a/application/src/main/data/json/system/scada_symbols/right-drain-pipe.svg b/application/src/main/data/json/system/scada_symbols/right-drain-pipe.svg index 093078c69b..a990e023e1 100644 --- a/application/src/main/data/json/system/scada_symbols/right-drain-pipe.svg +++ b/application/src/main/data/json/system/scada_symbols/right-drain-pipe.svg @@ -1,6 +1,6 @@ <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="200" fill="none" version="1.1" viewBox="0 0 400 200"><tb:metadata xmlns=""><![CDATA[{ "title": "Right drain pipe", - "description": "Right drain pipe", + "description": "Right drain pipe with configurable fluid and leak visualizations.", "searchTags": [ "pipe", "drain" diff --git a/application/src/main/data/json/system/scada_symbols/right-elbow-drain-pipe.svg b/application/src/main/data/json/system/scada_symbols/right-elbow-drain-pipe.svg index 63b143eeef..6f5ae5abb9 100644 --- a/application/src/main/data/json/system/scada_symbols/right-elbow-drain-pipe.svg +++ b/application/src/main/data/json/system/scada_symbols/right-elbow-drain-pipe.svg @@ -1,7 +1,7 @@ <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="200" fill="none" version="1.1" viewBox="0 0 400 200"> <tb:metadata xmlns=""><![CDATA[{ "title": "Right elbow drain pipe", - "description": "Right elbow drain pipe", + "description": "Right elbow drain pipe with configurable fluid and leak visualizations.", "searchTags": [ "pipe", "drain" diff --git a/application/src/main/data/json/system/scada_symbols/right-flow-meter.svg b/application/src/main/data/json/system/scada_symbols/right-flow-meter.svg index 9be5ec5857..6a201111ef 100644 --- a/application/src/main/data/json/system/scada_symbols/right-flow-meter.svg +++ b/application/src/main/data/json/system/scada_symbols/right-flow-meter.svg @@ -57,7 +57,7 @@ }, { "tag": "value", - "stateRenderFunction": "var value = ctx.values.value;\nctx.api.text(element, value.toFixed(0));\n", + "stateRenderFunction": "var value = ctx.api.formatValue(ctx.values.value, ctx.properties.valueDecimals, '', false);\nctx.api.text(element, value);\n", "actions": { "click": { "actionFunction": "ctx.api.callAction(event, 'displayClick');" @@ -463,16 +463,16 @@ "name": "{i18n:scada.symbol.units}", "type": "units", "default": "m³/hr", - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", + "fieldClass": "medium-width" + }, + { + "id": "valueDecimals", + "name": "{i18n:scada.symbol.decimals}", + "type": "number", + "default": 0, "fieldClass": "medium-width", - "min": null, - "max": null, - "step": null + "min": 0, + "step": 1 }, { "id": "defaultBorderColor", @@ -510,17 +510,7 @@ "rangeAdvanced": [] }, "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';" - }, - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + } }, { "id": "activeBorderColor", @@ -558,17 +548,7 @@ "rangeAdvanced": [] }, "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';" - }, - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + } }, { "id": "warningBorderColor", @@ -606,17 +586,7 @@ "rangeAdvanced": [] }, "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';" - }, - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + } }, { "id": "criticalBorderColor", @@ -654,17 +624,7 @@ "rangeAdvanced": [] }, "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';" - }, - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + } }, { "id": "backgroundColor", @@ -702,49 +662,19 @@ "rangeAdvanced": [] }, "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';" - }, - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + } }, { "id": "fluidColor", "name": "{i18n:scada.symbol.fluid-color}", "type": "color", - "default": "#1EC1F480", - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + "default": "#1EC1F480" }, { "id": "pipeColor", "name": "{i18n:scada.symbol.pipe-color}", "type": "color", - "default": "#FFFFFF", - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + "default": "#FFFFFF" } ] } diff --git a/application/src/main/data/json/system/scada_symbols/short-bottom-filter.svg b/application/src/main/data/json/system/scada_symbols/short-bottom-filter.svg index b3235c5906..cf191d40fe 100644 --- a/application/src/main/data/json/system/scada_symbols/short-bottom-filter.svg +++ b/application/src/main/data/json/system/scada_symbols/short-bottom-filter.svg @@ -1,7 +1,7 @@ { "title": "Short bottom filter", - "description": "Short bottom filter", + "description": "Short bottom filter with configurable click actions for custom operations, dashboard manipulation, etc.", "searchTags": [ "filter" ], diff --git a/application/src/main/data/json/system/scada_symbols/short-left-drain-pipe.svg b/application/src/main/data/json/system/scada_symbols/short-left-drain-pipe.svg index abf43aacb9..da2273d096 100644 --- a/application/src/main/data/json/system/scada_symbols/short-left-drain-pipe.svg +++ b/application/src/main/data/json/system/scada_symbols/short-left-drain-pipe.svg @@ -1,7 +1,7 @@ <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"> <tb:metadata><![CDATA[{ "title": "Short left drain pipe", - "description": "Short left drain pipe", + "description": "Short left drain pipe with configurable fluid and leak visualizations.", "searchTags": [ "pipe", "drain" diff --git a/application/src/main/data/json/system/scada_symbols/short-right-drain-pipe.svg b/application/src/main/data/json/system/scada_symbols/short-right-drain-pipe.svg index 1b2ae3934a..1e01fe8b77 100644 --- a/application/src/main/data/json/system/scada_symbols/short-right-drain-pipe.svg +++ b/application/src/main/data/json/system/scada_symbols/short-right-drain-pipe.svg @@ -1,7 +1,7 @@ <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"> <tb:metadata><![CDATA[{ "title": "Short right drain pipe", - "description": "Short right drain pipe", + "description": "Short right drain pipe with configurable fluid and leak visualizations.", "searchTags": [ "pipe", "drain" diff --git a/application/src/main/data/json/system/scada_symbols/short-top-filter.svg b/application/src/main/data/json/system/scada_symbols/short-top-filter.svg index d50eada2aa..0a77c78f00 100644 --- a/application/src/main/data/json/system/scada_symbols/short-top-filter.svg +++ b/application/src/main/data/json/system/scada_symbols/short-top-filter.svg @@ -1,6 +1,6 @@ <svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="400" fill="none" version="1.1" viewBox="0 0 200 400"> <tb:metadata xmlns=""><![CDATA[{ - "title": "Short top filter", + "title": "Short top filter with configurable click actions for custom operations, dashboard manipulation, etc.", "description": "Short top filter", "searchTags": [ "filter" diff --git a/application/src/main/data/json/system/scada_symbols/simple-vertical-scale-hp.svg b/application/src/main/data/json/system/scada_symbols/simple-vertical-scale-hp.svg index bfc356cca2..fa0c0fc4d1 100644 --- a/application/src/main/data/json/system/scada_symbols/simple-vertical-scale-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/simple-vertical-scale-hp.svg @@ -704,7 +704,7 @@ } ] } -Outdoor°C +Outdoor°C diff --git a/application/src/main/data/json/system/scada_symbols/single-key-switch-hp.svg b/application/src/main/data/json/system/scada_symbols/single-key-switch-hp.svg new file mode 100644 index 0000000000..77d6e7f95d --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/single-key-switch-hp.svg @@ -0,0 +1,373 @@ +{ + "title": "HP Single-key switch", + "description": "Single-key switch with various states.", + "searchTags": [ + "energy", + "power" + ], + "widgetSizeX": 1, + "widgetSizeY": 1, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "element.attr({fill: ctx.properties.backgroundColor});", + "actions": null + }, + { + "tag": "button", + "stateRenderFunction": "var color = ctx.properties.disabledColor;\nif (ctx.values.initialState) {\n color = ctx.properties.enabledColor;\n}\nelement.attr({fill: color});", + "actions": { + "click": { + "actionFunction": "var initial = ctx.values.initialState;\nvar action = initial ? 'offUpdateState' : 'onUpdateState';\n\nctx.api.callAction(event, action, undefined, {\n next: () => {\n ctx.api.setValue('initialState', !initial);\n }\n});" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "initialState", + "name": "{i18n:scada.symbol.on-off-state}", + "hint": "{i18n:scada.symbol.on-off-state-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": "{i18n:scada.symbol.on}", + "falseLabel": "{i18n:scada.symbol.off}", + "stateLabel": "", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "onUpdateState", + "name": "{i18n:scada.symbol.on-update-state}", + "hint": "{i18n:scada.symbol.on-update-state-hint}", + "group": null, + "type": "action", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": null, + "defaultSetValueSettings": { + "action": "EXECUTE_RPC", + "executeRpc": { + "method": "setState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "setAttribute": { + "scope": "SERVER_SCOPE", + "key": "state" + }, + "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": "offUpdateState", + "name": "{i18n:scada.symbol.off-update-state}", + "hint": "{i18n:scada.symbol.off-update-state-hint}", + "group": null, + "type": "action", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": null, + "defaultSetValueSettings": { + "action": "EXECUTE_RPC", + "executeRpc": { + "method": "setState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "setAttribute": { + "key": "state", + "scope": "SERVER_SCOPE" + }, + "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}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + } + ], + "properties": [ + { + "id": "backgroundColor", + "name": "{i18n:scada.symbol.background-color}", + "type": "color", + "default": "#FFFFFF", + "disabled": false, + "visible": true + }, + { + "id": "enabledColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "subLabel": "{i18n:scada.symbol.enabled}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "disabledColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "subLabel": "{i18n:scada.symbol.disabled}", + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "subLabel": "{i18n:scada.symbol.critical}", + "disabled": false, + "visible": true + } + ] +} + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/small-horizontal-separator-connector-hp.svg b/application/src/main/data/json/system/scada_symbols/small-horizontal-separator-connector-hp.svg new file mode 100644 index 0000000000..dedc4dc81e --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/small-horizontal-separator-connector-hp.svg @@ -0,0 +1,342 @@ +{ + "title": "HP Small horizontal separator with connector", + "description": "Small horizontal separator with connector with various states.", + "searchTags": [ + "extraction" + ], + "widgetSizeX": 5, + "widgetSizeY": 2, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "required": null, + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "required": null, + "subLabel": "{i18n:scada.symbol.stopped}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "required": null, + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "required": null, + "subLabel": "{i18n:scada.symbol.critical}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + } + ] +} + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/small-horizontal-separator-hp.svg b/application/src/main/data/json/system/scada_symbols/small-horizontal-separator-hp.svg new file mode 100644 index 0000000000..2bb0b1b244 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/small-horizontal-separator-hp.svg @@ -0,0 +1,342 @@ +{ + "title": "HP Small horizontal separator", + "description": "Small horizontal separator with various states.", + "searchTags": [ + "extraction" + ], + "widgetSizeX": 5, + "widgetSizeY": 2, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "required": null, + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "required": null, + "subLabel": "{i18n:scada.symbol.stopped}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "required": null, + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "required": null, + "subLabel": "{i18n:scada.symbol.critical}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + } + ] +} + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/small-power-transformer-hp.svg b/application/src/main/data/json/system/scada_symbols/small-power-transformer-hp.svg new file mode 100644 index 0000000000..049c75ab58 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/small-power-transformer-hp.svg @@ -0,0 +1,359 @@ +{ + "title": "HP Small power transformer", + "description": "Small power transformer with various states.", + "searchTags": [ + "power", + "energy" + ], + "widgetSizeX": 2, + "widgetSizeY": 2, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "required": null, + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "required": null, + "subLabel": "{i18n:scada.symbol.stopped}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "required": null, + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "required": null, + "subLabel": "{i18n:scada.symbol.critical}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + } + ] +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/small-vertical-separator-connector-hp.svg b/application/src/main/data/json/system/scada_symbols/small-vertical-separator-connector-hp.svg new file mode 100644 index 0000000000..b248996612 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/small-vertical-separator-connector-hp.svg @@ -0,0 +1,342 @@ +{ + "title": "HP Small vertical separator with connector", + "description": "Small vertical separator with connector with various states.", + "searchTags": [ + "extraction" + ], + "widgetSizeX": 2, + "widgetSizeY": 5, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "required": null, + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "required": null, + "subLabel": "{i18n:scada.symbol.stopped}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "required": null, + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "required": null, + "subLabel": "{i18n:scada.symbol.critical}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + } + ] +} + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/small-vertical-separator-hp.svg b/application/src/main/data/json/system/scada_symbols/small-vertical-separator-hp.svg new file mode 100644 index 0000000000..25aec080b3 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/small-vertical-separator-hp.svg @@ -0,0 +1,342 @@ +{ + "title": "HP Small vertical separator", + "description": "Small vertical separator with various states.", + "searchTags": [ + "extraction" + ], + "widgetSizeX": 2, + "widgetSizeY": 5, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "required": null, + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "required": null, + "subLabel": "{i18n:scada.symbol.stopped}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "required": null, + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "required": null, + "subLabel": "{i18n:scada.symbol.critical}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + } + ] +} + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/solar-panel-hp.svg b/application/src/main/data/json/system/scada_symbols/solar-panel-hp.svg new file mode 100644 index 0000000000..b7b7724f89 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/solar-panel-hp.svg @@ -0,0 +1,353 @@ +{ + "title": "HP Solar panel", + "description": "Solar panel with various states.", + "searchTags": [ + "energy", + "power", + "renewable", + "generation" + ], + "widgetSizeX": 1, + "widgetSizeY": 2, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "required": null, + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "required": null, + "subLabel": "{i18n:scada.symbol.stopped}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "required": null, + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "required": null, + "subLabel": "{i18n:scada.symbol.critical}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + } + ] +} + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/stand-solar-panel-hp.svg b/application/src/main/data/json/system/scada_symbols/stand-solar-panel-hp.svg new file mode 100644 index 0000000000..93e622a021 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/stand-solar-panel-hp.svg @@ -0,0 +1,357 @@ +{ + "title": "HP Stand solar panel", + "description": "Stand solar panel with various states.", + "searchTags": [ + "energy", + "power", + "renewable", + "generation" + ], + "widgetSizeX": 3, + "widgetSizeY": 2, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "required": null, + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "required": null, + "subLabel": "{i18n:scada.symbol.stopped}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "required": null, + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "required": null, + "subLabel": "{i18n:scada.symbol.critical}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + } + ] +} + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/three-rate-energy-meter-hp.svg b/application/src/main/data/json/system/scada_symbols/three-rate-energy-meter-hp.svg new file mode 100644 index 0000000000..f2b7bf4d54 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/three-rate-energy-meter-hp.svg @@ -0,0 +1,747 @@ +{ + "title": "HP Three-rate energy meter", + "description": "Three-rate energy meter with various states.", + "searchTags": [ + "power", + "energy" + ], + "widgetSizeX": 3, + "widgetSizeY": 2, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "night-label", + "stateRenderFunction": "if (ctx.properties.showNightLabel) {\n element.show();\n ctx.api.font(element, ctx.properties.nightLabelFont, ctx.properties.nightLabelColor);\n ctx.api.text(element, ctx.properties.nightLabel);\n} else {\n element.hide();\n}", + "actions": null + }, + { + "tag": "night-rate", + "stateRenderFunction": "ctx.api.font(element, ctx.properties.nightValueFont, ctx.properties.nightValueColor);\nctx.api.text(element, ctx.api.formatValue(ctx.values.nightRate, 0, null, 0));", + "actions": null + }, + { + "tag": "off-peak-label", + "stateRenderFunction": "if (ctx.properties.showOffPeakLabel) {\n element.show();\n ctx.api.font(element, ctx.properties.offPeakLabelFont, ctx.properties.offPeakLabelColor);\n ctx.api.text(element, ctx.properties.offPeakLabel);\n} else {\n element.hide();\n}", + "actions": null + }, + { + "tag": "off-peak-rate", + "stateRenderFunction": "ctx.api.font(element, ctx.properties.offPeakValueFont, ctx.properties.offPeakValueColor);\nctx.api.text(element, ctx.api.formatValue(ctx.values.offPeakRate, 0, null, 0));", + "actions": null + }, + { + "tag": "peak-label", + "stateRenderFunction": "if (ctx.properties.showPeakLabel) {\n element.show();\n ctx.api.font(element, ctx.properties.peakLabelFont, ctx.properties.peakLabelColor);\n ctx.api.text(element, ctx.properties.peakLabel);\n} else {\n element.hide();\n}", + "actions": null + }, + { + "tag": "peak-rate", + "stateRenderFunction": "ctx.api.font(element, ctx.properties.peakValueFont, ctx.properties.peakValueColor);\nctx.api.text(element, ctx.api.formatValue(ctx.values.peakRate, 0, null, 0));", + "actions": null + }, + { + "tag": "units", + "stateRenderFunction": "if (ctx.properties.showUnits) {\n element.show();\n ctx.api.font(element, ctx.properties.unitsFont, ctx.properties.unitsColor);\n ctx.api.text(element, ctx.properties.units);\n} else {\n element.hide();\n}", + "actions": null + }, + { + "tag": "value-box-night", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.nightValueBoxBackground;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "value-box-off-peak", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.offPeakValueBoxBackground;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "value-box-peak", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.peakValueBoxBackground;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "offPeakRate", + "name": "{i18n:scada.symbol.off-peak-rate}", + "hint": "{i18n:scada.symbol.measured-hint}", + "group": null, + "type": "value", + "valueType": "DOUBLE", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": { + "action": "GET_TIME_SERIES", + "defaultValue": null, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "offPeakRate" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "nightRate", + "name": "{i18n:scada.symbol.night-rate}", + "hint": "{i18n:scada.symbol.measured-hint}", + "group": null, + "type": "value", + "valueType": "DOUBLE", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": { + "action": "GET_TIME_SERIES", + "defaultValue": null, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "nightRate" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "peakRate", + "name": "{i18n:scada.symbol.peak-rate}", + "hint": "{i18n:scada.symbol.measured-hint}", + "group": null, + "type": "value", + "valueType": "DOUBLE", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": { + "action": "GET_TIME_SERIES", + "defaultValue": null, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "peakRate" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "subLabel": "{i18n:scada.symbol.stopped}", + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "subLabel": "{i18n:scada.symbol.critical}", + "disabled": false, + "visible": true + }, + { + "id": "showOffPeakLabel", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.off-peak-rate}", + "type": "switch", + "default": true, + "disabled": false, + "visible": true + }, + { + "id": "offPeakLabel", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.off-peak-rate}", + "type": "text", + "default": "T1", + "fieldClass": "medium-width", + "disabled": false, + "visible": true + }, + { + "id": "offPeakLabelFont", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.off-peak-rate}", + "type": "font", + "default": { + "size": 36, + "sizeUnit": "px", + "family": "Roboto", + "weight": "normal", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "offPeakLabelColor", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.off-peak-rate}", + "type": "color", + "default": "#000000", + "disabled": false, + "visible": true + }, + { + "id": "offPeakValueFont", + "name": "{i18n:scada.symbol.value}", + "group": "{i18n:scada.symbol.off-peak-rate}", + "type": "font", + "default": { + "size": 48, + "sizeUnit": "px", + "family": "Roboto", + "weight": "normal", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "offPeakValueColor", + "name": "{i18n:scada.symbol.value}", + "group": "{i18n:scada.symbol.off-peak-rate}", + "type": "color", + "default": "#002878", + "disabled": false, + "visible": true + }, + { + "id": "offPeakValueBoxBackground", + "name": "{i18n:scada.symbol.value-box-background}", + "group": "{i18n:scada.symbol.off-peak-rate}", + "type": "color", + "default": "#DEDEDE", + "disabled": false, + "visible": true + }, + { + "id": "showNightLabel", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.night-rate}", + "type": "switch", + "default": true, + "disabled": false, + "visible": true + }, + { + "id": "nightLabel", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.night-rate}", + "type": "text", + "default": "T2", + "fieldClass": "medium-width", + "disabled": false, + "visible": true + }, + { + "id": "nightLabelFont", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.night-rate}", + "type": "font", + "default": { + "size": 36, + "sizeUnit": "px", + "family": "Roboto", + "weight": "normal", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "nightLabelColor", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.night-rate}", + "type": "color", + "default": "#000", + "disabled": false, + "visible": true + }, + { + "id": "nightValueFont", + "name": "{i18n:scada.symbol.value}", + "group": "{i18n:scada.symbol.night-rate}", + "type": "font", + "default": { + "size": 48, + "sizeUnit": "px", + "family": "Roboto", + "weight": "normal", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "nightValueColor", + "name": "{i18n:scada.symbol.value}", + "group": "{i18n:scada.symbol.night-rate}", + "type": "color", + "default": "#002878", + "disabled": false, + "visible": true + }, + { + "id": "nightValueBoxBackground", + "name": "{i18n:scada.symbol.value-box-background}", + "group": "{i18n:scada.symbol.night-rate}", + "type": "color", + "default": "#DEDEDE", + "disabled": false, + "visible": true + }, + { + "id": "showPeakLabel", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.peak-rate}", + "type": "switch", + "default": true, + "disabled": false, + "visible": true + }, + { + "id": "peakLabel", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.peak-rate}", + "type": "text", + "default": "T3", + "fieldClass": "medium-width", + "disabled": false, + "visible": true + }, + { + "id": "peakLabelFont", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.peak-rate}", + "type": "font", + "default": { + "size": 36, + "sizeUnit": "px", + "family": "Roboto", + "weight": "normal", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "peakLabelColor", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.peak-rate}", + "type": "color", + "default": "#000", + "disabled": false, + "visible": true + }, + { + "id": "peakValueFont", + "name": "{i18n:scada.symbol.value}", + "group": "{i18n:scada.symbol.peak-rate}", + "type": "font", + "default": { + "size": 48, + "sizeUnit": "px", + "family": "Roboto", + "weight": "normal", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "peakValueColor", + "name": "{i18n:scada.symbol.value}", + "group": "{i18n:scada.symbol.peak-rate}", + "type": "color", + "default": "#002878", + "disabled": false, + "visible": true + }, + { + "id": "peakValueBoxBackground", + "name": "{i18n:scada.symbol.value-box-background}", + "group": "{i18n:scada.symbol.peak-rate}", + "type": "color", + "default": "#DEDEDE", + "disabled": false, + "visible": true + }, + { + "id": "showUnits", + "name": "{i18n:scada.symbol.units}", + "type": "switch", + "default": true, + "disabled": false, + "visible": true + }, + { + "id": "units", + "name": "{i18n:scada.symbol.units}", + "type": "units", + "default": "kWh", + "disabled": false, + "visible": true + }, + { + "id": "unitsFont", + "name": "{i18n:scada.symbol.units}", + "type": "font", + "default": { + "size": 36, + "sizeUnit": "px", + "family": "Roboto", + "weight": "normal", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "unitsColor", + "name": "{i18n:scada.symbol.units}", + "type": "color", + "default": "#000", + "disabled": false, + "visible": true + } + ] +} +T1T2T3000223000223000223kWh + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/top-flow-meter.svg b/application/src/main/data/json/system/scada_symbols/top-flow-meter.svg index 619bb48ef8..9c09a6067e 100644 --- a/application/src/main/data/json/system/scada_symbols/top-flow-meter.svg +++ b/application/src/main/data/json/system/scada_symbols/top-flow-meter.svg @@ -57,7 +57,7 @@ }, { "tag": "value", - "stateRenderFunction": "var value = ctx.values.value;\nctx.api.text(element, value.toFixed(0));\n", + "stateRenderFunction": "var value = ctx.api.formatValue(ctx.values.value, ctx.properties.valueDecimals, '', false);\nctx.api.text(element, value);\n", "actions": { "click": { "actionFunction": "ctx.api.callAction(event, 'displayClick');" @@ -463,16 +463,20 @@ "name": "{i18n:scada.symbol.units}", "type": "units", "default": "m³/hr", - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", "fieldClass": "medium-width", - "min": null, - "max": null, - "step": null + "disabled": false, + "visible": true + }, + { + "id": "valueDecimals", + "name": "{i18n:scada.symbol.decimals}", + "type": "number", + "default": 0, + "fieldClass": "medium-width", + "min": 0, + "step": 1, + "disabled": false, + "visible": true }, { "id": "defaultBorderColor", @@ -511,16 +515,8 @@ }, "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';" }, - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + "disabled": false, + "visible": true }, { "id": "activeBorderColor", @@ -559,16 +555,8 @@ }, "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';" }, - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + "disabled": false, + "visible": true }, { "id": "warningBorderColor", @@ -607,16 +595,8 @@ }, "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';" }, - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + "disabled": false, + "visible": true }, { "id": "criticalBorderColor", @@ -655,16 +635,8 @@ }, "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';" }, - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + "disabled": false, + "visible": true }, { "id": "backgroundColor", @@ -703,48 +675,24 @@ }, "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';" }, - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + "disabled": false, + "visible": true }, { "id": "fluidColor", "name": "{i18n:scada.symbol.fluid-color}", "type": "color", "default": "#1EC1F480", - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + "disabled": false, + "visible": true }, { "id": "pipeColor", "name": "{i18n:scada.symbol.pipe-color}", "type": "color", "default": "#FFFFFF", - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + "disabled": false, + "visible": true } ] }]]> diff --git a/application/src/main/data/json/system/scada_symbols/top-light-bulb-hp.svg b/application/src/main/data/json/system/scada_symbols/top-light-bulb-hp.svg new file mode 100644 index 0000000000..ed6855884a --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/top-light-bulb-hp.svg @@ -0,0 +1,346 @@ +{ + "title": "HP Top light bulb", + "description": "Top light bulb with various states.", + "searchTags": [ + "energy" + ], + "widgetSizeX": 1, + "widgetSizeY": 1, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "required": null, + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "required": null, + "subLabel": "{i18n:scada.symbol.stopped}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "required": null, + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "required": null, + "subLabel": "{i18n:scada.symbol.critical}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null + } + ] +} + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/turbine-hp.svg b/application/src/main/data/json/system/scada_symbols/turbine-hp.svg new file mode 100644 index 0000000000..d798b09477 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/turbine-hp.svg @@ -0,0 +1,377 @@ +{ + "title": "HP Turbine", + "description": "Turbine with various states.", + "searchTags": [ + "extraction" + ], + "widgetSizeX": 4, + "widgetSizeY": 2, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "secondary-background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = '#999';\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "required": null, + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "required": null, + "subLabel": "{i18n:scada.symbol.stopped}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "required": null, + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "required": null, + "subLabel": "{i18n:scada.symbol.critical}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + } + ] +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/two-key-switch-hp.svg b/application/src/main/data/json/system/scada_symbols/two-key-switch-hp.svg new file mode 100644 index 0000000000..40e04a3bf5 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/two-key-switch-hp.svg @@ -0,0 +1,489 @@ +{ + "title": "HP Two-key switch", + "description": "Two-key switch with various states.", + "searchTags": [ + "energy", + "power" + ], + "widgetSizeX": 1, + "widgetSizeY": 1, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "element.attr({fill: ctx.properties.backgroundColor});", + "actions": null + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "left-button", + "stateRenderFunction": "var color = ctx.properties.disabledColor;\nif (ctx.values.initialStateLeftButton) {\n color = ctx.properties.enabledColor;\n}\nelement.attr({fill: color});", + "actions": { + "click": { + "actionFunction": "var initial = ctx.values.initialStateLeftButton;\nvar action = initial ? 'offUpdateStateLeftButton' : 'onUpdateStateLeftButton';\n\nctx.api.callAction(event, action, undefined, {\n next: () => {\n ctx.api.setValue('initialStateLeftButton', !initial);\n }\n});" + } + } + }, + { + "tag": "right-button", + "stateRenderFunction": "var color = ctx.properties.disabledColor;\nif (ctx.values.initialStateRightButton) {\n color = ctx.properties.enabledColor;\n}\nelement.attr({fill: color});", + "actions": { + "click": { + "actionFunction": "var initial = ctx.values.initialStateRightButton;\nvar action = initial ? 'offUpdateStateRightButton' : 'onUpdateStateRightButton';\n\nctx.api.callAction(event, action, undefined, {\n next: () => {\n ctx.api.setValue('initialStateRightButton', !initial);\n }\n});" + } + } + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "initialStateLeftButton", + "name": "{i18n:scada.symbol.on-off-state}", + "hint": "{i18n:scada.symbol.on-off-state-hint}", + "group": "{i18n:scada.symbol.left-button}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": "{i18n:scada.symbol.on}", + "falseLabel": "{i18n:scada.symbol.off}", + "stateLabel": "", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "leftButton" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "onUpdateStateLeftButton", + "name": "{i18n:scada.symbol.on-update-state}", + "hint": "{i18n:scada.symbol.on-update-state-hint}", + "group": "{i18n:scada.symbol.left-button}", + "type": "action", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": null, + "defaultSetValueSettings": { + "action": "EXECUTE_RPC", + "executeRpc": { + "method": "setState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "setAttribute": { + "scope": "SERVER_SCOPE", + "key": "state" + }, + "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": "offUpdateStateLeftButton", + "name": "{i18n:scada.symbol.off-update-state}", + "hint": "{i18n:scada.symbol.off-update-state-hint}", + "group": "{i18n:scada.symbol.left-button}", + "type": "action", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": null, + "defaultSetValueSettings": { + "action": "EXECUTE_RPC", + "executeRpc": { + "method": "setState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "setAttribute": { + "key": "state", + "scope": "SERVER_SCOPE" + }, + "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": "initialStateRightButton", + "name": "{i18n:scada.symbol.on-off-state}", + "hint": "{i18n:scada.symbol.on-off-state-hint}", + "group": "{i18n:scada.symbol.right-button}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": "{i18n:scada.symbol.on}", + "falseLabel": "{i18n:scada.symbol.off}", + "stateLabel": null, + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "rightButton" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "onUpdateStateRightButton", + "name": "{i18n:scada.symbol.on-update-state}", + "hint": "{i18n:scada.symbol.on-update-state-hint}", + "group": "{i18n:scada.symbol.right-button}", + "type": "action", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": null, + "defaultSetValueSettings": { + "action": "EXECUTE_RPC", + "executeRpc": { + "method": "setState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "setAttribute": { + "scope": "SERVER_SCOPE", + "key": "state" + }, + "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": "offUpdateStateRightButton", + "name": "{i18n:scada.symbol.off-update-state}", + "hint": "{i18n:scada.symbol.off-update-state-hint}", + "group": "{i18n:scada.symbol.right-button}", + "type": "action", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": null, + "defaultSetValueSettings": { + "action": "EXECUTE_RPC", + "executeRpc": { + "method": "setState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "setAttribute": { + "key": "state", + "scope": "SERVER_SCOPE" + }, + "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}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + } + ], + "properties": [ + { + "id": "backgroundColor", + "name": "{i18n:scada.symbol.background-color}", + "type": "color", + "default": "#FFFFFF", + "disabled": false, + "visible": true + }, + { + "id": "enabledColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "subLabel": "{i18n:scada.symbol.enabled}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "disabledColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "subLabel": "{i18n:scada.symbol.disabled}", + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "subLabel": "{i18n:scada.symbol.critical}", + "disabled": false, + "visible": true + } + ] +} + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/two-rate-energy-meter-hp.svg b/application/src/main/data/json/system/scada_symbols/two-rate-energy-meter-hp.svg new file mode 100644 index 0000000000..7adc401836 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/two-rate-energy-meter-hp.svg @@ -0,0 +1,618 @@ +{ + "title": "HP Two-rate energy meter", + "description": "Two-rate energy meter with various states.", + "searchTags": [ + "power", + "energy" + ], + "widgetSizeX": 2, + "widgetSizeY": 2, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "day-label", + "stateRenderFunction": "if (ctx.properties.showDayLabel) {\n element.show();\n ctx.api.font(element, ctx.properties.dayLabelFont, ctx.properties.dayLabelColor);\n ctx.api.text(element, ctx.properties.dayLabel);\n} else {\n element.hide();\n}", + "actions": null + }, + { + "tag": "day-rate", + "stateRenderFunction": "ctx.api.font(element, ctx.properties.dayValueFont, ctx.properties.dayValueColor);\nctx.api.text(element, ctx.api.formatValue(ctx.values.dayRate, 0, null, 0));", + "actions": null + }, + { + "tag": "night-label", + "stateRenderFunction": "if (ctx.properties.showNightLabel) {\n element.show();\n ctx.api.font(element, ctx.properties.nightLabelFont, ctx.properties.nightLabelColor);\n ctx.api.text(element, ctx.properties.nightLabel);\n} else {\n element.hide();\n}", + "actions": null + }, + { + "tag": "night-rate", + "stateRenderFunction": "ctx.api.font(element, ctx.properties.nightValueFont, ctx.properties.nightValueColor);\nctx.api.text(element, ctx.api.formatValue(ctx.values.nightRate, 0, null, 0));", + "actions": null + }, + { + "tag": "units", + "stateRenderFunction": "if (ctx.properties.showUnits) {\n element.show();\n ctx.api.font(element, ctx.properties.unitsFont, ctx.properties.unitsColor);\n ctx.api.text(element, ctx.properties.units);\n} else {\n element.hide();\n}", + "actions": null + }, + { + "tag": "value-box-day", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.dayValueBoxBackground;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "value-box-night", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.nightValueBoxBackground;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "dayRate", + "name": "{i18n:scada.symbol.day-rate}", + "hint": "{i18n:scada.symbol.measured-hint}", + "group": null, + "type": "value", + "valueType": "DOUBLE", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": { + "action": "GET_TIME_SERIES", + "defaultValue": null, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "dayRate" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "nightRate", + "name": "{i18n:scada.symbol.night-rate}", + "hint": "{i18n:scada.symbol.measured-hint}", + "group": null, + "type": "value", + "valueType": "DOUBLE", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": { + "action": "GET_TIME_SERIES", + "defaultValue": null, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "nightRate" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "subLabel": "{i18n:scada.symbol.stopped}", + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "subLabel": "{i18n:scada.symbol.critical}", + "disabled": false, + "visible": true + }, + { + "id": "showDayLabel", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.day-rate}", + "type": "switch", + "default": true, + "disabled": false, + "visible": true + }, + { + "id": "dayLabel", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.day-rate}", + "type": "text", + "default": "T1", + "fieldClass": "medium-width", + "disabled": false, + "visible": true + }, + { + "id": "dayLabelFont", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.day-rate}", + "type": "font", + "default": { + "size": 36, + "sizeUnit": "px", + "family": "Roboto", + "weight": "normal", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "dayLabelColor", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.day-rate}", + "type": "color", + "default": "#000000", + "disabled": false, + "visible": true + }, + { + "id": "dayValueFont", + "name": "{i18n:scada.symbol.value}", + "group": "{i18n:scada.symbol.day-rate}", + "type": "font", + "default": { + "size": 48, + "sizeUnit": "px", + "family": "Roboto", + "weight": "normal", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "dayValueColor", + "name": "{i18n:scada.symbol.value}", + "group": "{i18n:scada.symbol.day-rate}", + "type": "color", + "default": "#002878", + "disabled": false, + "visible": true + }, + { + "id": "dayValueBoxBackground", + "name": "{i18n:scada.symbol.value-box-background}", + "group": "{i18n:scada.symbol.day-rate}", + "type": "color", + "default": "#DEDEDE", + "disabled": false, + "visible": true + }, + { + "id": "showNightLabel", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.night-rate}", + "type": "switch", + "default": true, + "disabled": false, + "visible": true + }, + { + "id": "nightLabel", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.night-rate}", + "type": "text", + "default": "T2", + "fieldClass": "medium-width", + "disabled": false, + "visible": true + }, + { + "id": "nightLabelFont", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.night-rate}", + "type": "font", + "default": { + "size": 36, + "sizeUnit": "px", + "family": "Roboto", + "weight": "normal", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "nightLabelColor", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.night-rate}", + "type": "color", + "default": "#000", + "disabled": false, + "visible": true + }, + { + "id": "nightValueFont", + "name": "{i18n:scada.symbol.value}", + "group": "{i18n:scada.symbol.night-rate}", + "type": "font", + "default": { + "size": 48, + "sizeUnit": "px", + "family": "Roboto", + "weight": "normal", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "nightValueColor", + "name": "{i18n:scada.symbol.value}", + "group": "{i18n:scada.symbol.night-rate}", + "type": "color", + "default": "#002878", + "disabled": false, + "visible": true + }, + { + "id": "nightValueBoxBackground", + "name": "{i18n:scada.symbol.value-box-background}", + "group": "{i18n:scada.symbol.night-rate}", + "type": "color", + "default": "#DEDEDE", + "disabled": false, + "visible": true + }, + { + "id": "showUnits", + "name": "{i18n:scada.symbol.units}", + "type": "switch", + "default": true, + "disabled": false, + "visible": true + }, + { + "id": "units", + "name": "{i18n:scada.symbol.units}", + "type": "units", + "default": "kWh", + "disabled": false, + "visible": true + }, + { + "id": "unitsFont", + "name": "{i18n:scada.symbol.units}", + "type": "font", + "default": { + "size": 36, + "sizeUnit": "px", + "family": "Roboto", + "weight": "normal", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "unitsColor", + "name": "{i18n:scada.symbol.units}", + "type": "color", + "default": "#000", + "disabled": false, + "visible": true + } + ] +} +T1T2000023000023kWh + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/vertical-connector-hp.svg b/application/src/main/data/json/system/scada_symbols/vertical-connector-hp.svg index aea94b0dd2..6ce9336fee 100644 --- a/application/src/main/data/json/system/scada_symbols/vertical-connector-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/vertical-connector-hp.svg @@ -1,6 +1,6 @@ { "title": "HP Vertical connector", - "description": "Vertical connector", + "description": "Vertical connector with an optional directional arrow to visually indicate flow.", "widgetSizeX": 1, "widgetSizeY": 1, "tags": [ diff --git a/application/src/main/data/json/system/scada_symbols/vertical-energy-system-controller-hp.svg b/application/src/main/data/json/system/scada_symbols/vertical-energy-system-controller-hp.svg new file mode 100644 index 0000000000..cebe949c36 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/vertical-energy-system-controller-hp.svg @@ -0,0 +1,378 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="600" fill="none" version="1.1" viewBox="0 0 400 600"><tb:metadata xmlns=""><![CDATA[{ + "title": "HP Vertical energy systems controller", + "description": "Vertical energy systems controller with various states.", + "searchTags": [ + "energy", + "power", + "monitoring" + ], + "widgetSizeX": 2, + "widgetSizeY": 3, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.disconnectedColor;\nif (ctx.values.connected) {\n color = ctx.properties.connectedColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "indicator", + "stateRenderFunction": "var color = ctx.properties.disconnectedIndicatorColor;\nif (ctx.values.connected) {\n color = ctx.properties.connectedIndicatorColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "label", + "stateRenderFunction": "ctx.api.font(element, ctx.properties.labelFont, ctx.properties.labelColor);\n ctx.api.text(element, ctx.properties.label);", + "actions": null + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "connected", + "name": "{i18n:scada.symbol.connected}", + "hint": "{i18n:scada.symbol.connected-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.connected}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "connected" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "connectedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "subLabel": "{i18n:scada.symbol.connected}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "disconnectedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "subLabel": "{i18n:scada.symbol.disconnected}", + "disabled": false, + "visible": true + }, + { + "id": "connectedIndicatorColor", + "name": "{i18n:scada.symbol.indicator}", + "type": "color", + "default": "#198038", + "subLabel": "{i18n:scada.symbol.connected}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "disconnectedIndicatorColor", + "name": "{i18n:scada.symbol.indicator}", + "type": "color", + "default": "#DEDEDE", + "subLabel": "{i18n:scada.symbol.disconnected}", + "disabled": false, + "visible": true + }, + { + "id": "label", + "name": "{i18n:scada.symbol.label}", + "type": "text", + "default": "Connected", + "fieldClass": "medium-width", + "disabled": false, + "visible": true + }, + { + "id": "labelFont", + "name": "{i18n:scada.symbol.label}", + "type": "font", + "default": { + "size": 30, + "sizeUnit": "px", + "family": "Roboto", + "weight": "normal", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "labelColor", + "name": "{i18n:scada.symbol.label}", + "type": "color", + "default": "#000", + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "subLabel": "{i18n:scada.symbol.critical}", + "disabled": false, + "visible": true + } + ] +} +Connected + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/vertical-inline-flow-meter.svg b/application/src/main/data/json/system/scada_symbols/vertical-inline-flow-meter.svg index fff4819e1e..8f9a30e576 100644 --- a/application/src/main/data/json/system/scada_symbols/vertical-inline-flow-meter.svg +++ b/application/src/main/data/json/system/scada_symbols/vertical-inline-flow-meter.svg @@ -57,7 +57,7 @@ }, { "tag": "value", - "stateRenderFunction": "var value = ctx.values.value;\nctx.api.text(element, value.toFixed(0));\n", + "stateRenderFunction": "var value = ctx.api.formatValue(ctx.values.value, ctx.properties.valueDecimals, '', false);\nctx.api.text(element, value);\n", "actions": { "click": { "actionFunction": "ctx.api.callAction(event, 'displayClick');" @@ -463,16 +463,20 @@ "name": "{i18n:scada.symbol.units}", "type": "units", "default": "m³/hr", - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", "fieldClass": "medium-width", - "min": null, - "max": null, - "step": null + "disabled": false, + "visible": true + }, + { + "id": "valueDecimals", + "name": "{i18n:scada.symbol.decimals}", + "type": "number", + "default": 0, + "fieldClass": "medium-width", + "min": 0, + "step": 1, + "disabled": false, + "visible": true }, { "id": "defaultBorderColor", @@ -511,16 +515,8 @@ }, "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';" }, - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + "disabled": false, + "visible": true }, { "id": "activeBorderColor", @@ -559,16 +555,8 @@ }, "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';" }, - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + "disabled": false, + "visible": true }, { "id": "warningBorderColor", @@ -607,16 +595,8 @@ }, "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';" }, - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + "disabled": false, + "visible": true }, { "id": "criticalBorderColor", @@ -655,16 +635,8 @@ }, "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';" }, - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + "disabled": false, + "visible": true }, { "id": "backgroundColor", @@ -703,48 +675,24 @@ }, "colorFunction": "var temperature = value;\nif (typeof temperature !== undefined) {\n var percent = (temperature + 60)/120 * 100;\n return tinycolor.mix('blue', 'red', percent).toHexString();\n}\nreturn 'blue';" }, - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + "disabled": false, + "visible": true }, { "id": "fluidColor", "name": "{i18n:scada.symbol.fluid-color}", "type": "color", "default": "#1EC1F480", - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + "disabled": false, + "visible": true }, { "id": "pipeColor", "name": "{i18n:scada.symbol.pipe-color}", "type": "color", "default": "#FFFFFF", - "required": null, - "subLabel": null, - "divider": null, - "fieldSuffix": null, - "disableOnProperty": null, - "rowClass": "", - "fieldClass": "", - "min": null, - "max": null, - "step": null + "disabled": false, + "visible": true } ] }]]> diff --git a/application/src/main/data/json/system/scada_symbols/voltage-relay-hp.svg b/application/src/main/data/json/system/scada_symbols/voltage-relay-hp.svg new file mode 100644 index 0000000000..eaa0b02455 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/voltage-relay-hp.svg @@ -0,0 +1,439 @@ +{ + "title": "HP Voltage relay", + "description": "One phase voltage relay with various states.", + "searchTags": [ + "energy", + "power", + "protection" + ], + "widgetSizeX": 1, + "widgetSizeY": 2, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "units", + "stateRenderFunction": "if (ctx.properties.showUnits) {\n element.show();\n ctx.api.font(element, ctx.properties.unitsFont, ctx.properties.unitsColor);\n ctx.api.text(element, ctx.properties.units);\n} else {\n element.hide();\n}", + "actions": null + }, + { + "tag": "value", + "stateRenderFunction": "if (ctx.values.running) {\n element.show();\n ctx.api.font(element, ctx.properties.currentVoltageFont, ctx.properties.currentVoltageColor);\n ctx.api.text(element, ctx.api.formatValue(ctx.values.voltage, 0, null, 0));\n} else {\n element.hide();\n}", + "actions": null + }, + { + "tag": "value-box", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.valueBoxBackground;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "voltage", + "name": "{i18n:scada.symbol.voltage}", + "hint": "{i18n:scada.symbol.voltage-hint}", + "group": null, + "type": "value", + "valueType": "DOUBLE", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": { + "action": "GET_TIME_SERIES", + "defaultValue": null, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "voltage" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "subLabel": "{i18n:scada.symbol.stopped}", + "disabled": false, + "visible": true + }, + { + "id": "valueBoxBackground", + "name": "{i18n:scada.symbol.value-box-background}", + "type": "color", + "default": "#DEDEDE", + "disabled": false, + "visible": true + }, + { + "id": "currentVoltageFont", + "name": "{i18n:scada.symbol.current-voltage-color}", + "type": "font", + "default": { + "size": 32, + "sizeUnit": "px", + "family": "Roboto", + "weight": "400", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "currentVoltageColor", + "name": "{i18n:scada.symbol.current-voltage-color}", + "type": "color", + "default": "#002878", + "disabled": false, + "visible": true + }, + { + "id": "showUnits", + "name": "{i18n:scada.symbol.units}", + "type": "switch", + "default": true, + "disabled": false, + "visible": true + }, + { + "id": "units", + "name": "{i18n:scada.symbol.units}", + "type": "units", + "default": "V", + "disabled": false, + "visible": true + }, + { + "id": "unitsFont", + "name": "{i18n:scada.symbol.units}", + "type": "font", + "default": { + "size": 24, + "sizeUnit": "px", + "family": "Roboto", + "weight": "500", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "unitsColor", + "name": "{i18n:scada.symbol.units}", + "type": "color", + "default": "#000000", + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "subLabel": "{i18n:scada.symbol.critical}", + "disabled": false, + "visible": true + } + ] +} +220v + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/voltage-stabilizer-hp.svg b/application/src/main/data/json/system/scada_symbols/voltage-stabilizer-hp.svg new file mode 100644 index 0000000000..aafc2af5a0 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/voltage-stabilizer-hp.svg @@ -0,0 +1,584 @@ +{ + "title": "HP Voltage stabilizer", + "description": "Voltage stabilizer with various states.", + "searchTags": [ + "power", + "energy", + "protection" + ], + "widgetSizeX": 2, + "widgetSizeY": 1, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.operatingMode === 1) {\n color = ctx.properties.runningColor;\n} else if (ctx.values.operatingMode === 2) {\n color = ctx.properties.bypassColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "in-label", + "stateRenderFunction": "if (ctx.properties.showInputLabel) {\n element.show();\n ctx.api.font(element, ctx.properties.inputLabelFont, ctx.properties.inputLabelColor);\n ctx.api.text(element, ctx.properties.inputLabel);\n} else {\n element.hide();\n}", + "actions": null + }, + { + "tag": "in-value", + "stateRenderFunction": "if (ctx.values.operatingMode === 1) {\n element.show();\n ctx.api.font(element, ctx.properties.inputValutFont, ctx.properties.inputValueColor);\n ctx.api.text(element, ctx.api.formatValue(ctx.values.inputVoltage, 0, null, 0));\n} else {\n element.hide();\n}\n", + "actions": null + }, + { + "tag": "in-value-box", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.operatingMode === 1) {\n color = ctx.properties.inputValueBoxBackground;\n} else if (ctx.values.operatingMode === 2) {\n color = ctx.properties.bypassColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "out-label", + "stateRenderFunction": "if (ctx.properties.showOutputLabel) {\n element.show();\n ctx.api.font(element, ctx.properties.outputLabelFont, ctx.properties.outputLabelColor);\n ctx.api.text(element, ctx.properties.outputLabel);\n} else {\n element.hide();\n}", + "actions": null + }, + { + "tag": "out-value", + "stateRenderFunction": "if (ctx.values.operatingMode === 1) {\n element.show();\n ctx.api.font(element, ctx.properties.outputValueFont, ctx.properties.outputValueColor);\n ctx.api.text(element, ctx.api.formatValue(ctx.values.outputVoltage, 0, null, 0));\n} else {\n element.hide();\n}\n", + "actions": null + }, + { + "tag": "out-value-box", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.operatingMode === 1) {\n color = ctx.properties.outputValueBoxBackground;\n} else if (ctx.values.operatingMode === 2) {\n color = ctx.properties.bypassColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "operatingMode", + "name": "{i18n:scada.symbol.operating-mode}", + "hint": "{i18n:scada.symbol.operating-mode-hint}", + "group": null, + "type": "value", + "valueType": "INTEGER", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": null, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "operatingMode" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "inputVoltage", + "name": "{i18n:scada.symbol.input-voltage}", + "hint": "{i18n:scada.symbol.input-voltage-hint}", + "group": null, + "type": "value", + "valueType": "DOUBLE", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": { + "action": "GET_TIME_SERIES", + "defaultValue": null, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "inputVoltage" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "outputVoltage", + "name": "{i18n:scada.symbol.output-voltage}", + "hint": "{i18n:scada.symbol.output-voltage-hint}", + "group": null, + "type": "value", + "valueType": "DOUBLE", + "trueLabel": null, + "falseLabel": null, + "stateLabel": null, + "defaultGetValueSettings": { + "action": "GET_TIME_SERIES", + "defaultValue": null, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "outputVoltage" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "bypassColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#DEDEDE", + "subLabel": "{i18n:scada.symbol.bypass-mode}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "subLabel": "{i18n:scada.symbol.stopped}", + "divider": false, + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "subLabel": "{i18n:scada.symbol.critical}", + "disabled": false, + "visible": true + }, + { + "id": "showInputLabel", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.input-voltage}", + "type": "switch", + "default": true, + "disabled": false, + "visible": true + }, + { + "id": "inputLabel", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.input-voltage}", + "type": "text", + "default": "in", + "disabled": false, + "visible": true + }, + { + "id": "inputLabelFont", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.input-voltage}", + "type": "font", + "default": { + "size": 36, + "sizeUnit": "px", + "family": "Roboto", + "weight": "normal", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "inputLabelColor", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.input-voltage}", + "type": "color", + "default": "#000", + "disabled": false, + "visible": true + }, + { + "id": "inputValutFont", + "name": "{i18n:scada.symbol.value}", + "group": "{i18n:scada.symbol.input-voltage}", + "type": "font", + "default": { + "size": 44, + "sizeUnit": "px", + "family": "Roboto", + "weight": "normal", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "inputValueColor", + "name": "{i18n:scada.symbol.value}", + "group": "{i18n:scada.symbol.input-voltage}", + "type": "color", + "default": "#002878", + "disabled": false, + "visible": true + }, + { + "id": "inputValueBoxBackground", + "name": "{i18n:scada.symbol.value-box-background}", + "group": "{i18n:scada.symbol.input-voltage}", + "type": "color", + "default": "#DEDEDE", + "disabled": false, + "visible": true + }, + { + "id": "showOutputLabel", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.output-voltage}", + "type": "switch", + "default": true, + "disabled": false, + "visible": true + }, + { + "id": "outputLabel", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.output-voltage}", + "type": "text", + "default": "out", + "disabled": false, + "visible": true + }, + { + "id": "outputLabelFont", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.output-voltage}", + "type": "font", + "default": { + "size": 36, + "sizeUnit": "px", + "family": "Roboto", + "weight": "normal", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "outputLabelColor", + "name": "{i18n:scada.symbol.label}", + "group": "{i18n:scada.symbol.output-voltage}", + "type": "color", + "default": "#000", + "disabled": false, + "visible": true + }, + { + "id": "outputValueFont", + "name": "{i18n:scada.symbol.value}", + "group": "{i18n:scada.symbol.output-voltage}", + "type": "font", + "default": { + "size": 44, + "sizeUnit": "px", + "family": "Roboto", + "weight": "normal", + "style": "normal" + }, + "disabled": false, + "visible": true + }, + { + "id": "outputValueColor", + "name": "{i18n:scada.symbol.value}", + "group": "{i18n:scada.symbol.output-voltage}", + "type": "color", + "default": "#002878", + "disabled": false, + "visible": true + }, + { + "id": "outputValueBoxBackground", + "name": "{i18n:scada.symbol.value-box-background}", + "group": "{i18n:scada.symbol.output-voltage}", + "type": "color", + "default": "#DEDEDE", + "disabled": false, + "visible": true + } + ] +} +220230inout + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/wind-turbine-cluster-hp.svg b/application/src/main/data/json/system/scada_symbols/wind-turbine-cluster-hp.svg new file mode 100644 index 0000000000..74855dd35a --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/wind-turbine-cluster-hp.svg @@ -0,0 +1,370 @@ +{ + "title": "HP Wind turbine cluster", + "description": "Wind turbine cluster with various states.", + "searchTags": [ + "energy", + "power", + "renewable", + "generation" + ], + "widgetSizeX": 3, + "widgetSizeY": 4, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "required": null, + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "required": null, + "subLabel": "{i18n:scada.symbol.stopped}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "required": null, + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "required": null, + "subLabel": "{i18n:scada.symbol.critical}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + } + ] +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/wind-turbine-hp.svg b/application/src/main/data/json/system/scada_symbols/wind-turbine-hp.svg new file mode 100644 index 0000000000..a4282c16e7 --- /dev/null +++ b/application/src/main/data/json/system/scada_symbols/wind-turbine-hp.svg @@ -0,0 +1,360 @@ +{ + "title": "HP Wind turbine", + "description": "Wind turbine with various states.", + "searchTags": [ + "energy", + "power", + "renewable", + "generation" + ], + "widgetSizeX": 3, + "widgetSizeY": 4, + "tags": [ + { + "tag": "background", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});", + "actions": null + }, + { + "tag": "clickArea", + "stateRenderFunction": null, + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'click');" + } + } + }, + { + "tag": "critical", + "stateRenderFunction": "element.attr({fill: ctx.properties.criticalColor});\nif (ctx.values.critical) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'criticalClick');" + } + } + }, + { + "tag": "warning", + "stateRenderFunction": "element.attr({fill: ctx.properties.warningColor});\nvar warning = ctx.values.warning && !(ctx.values.warning && ctx.values.critical)\nif (warning) {\n element.show();\n} else {\n element.hide();\n}\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = warning && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", + "actions": { + "click": { + "actionFunction": "ctx.api.callAction(event, 'warningClick');" + } + } + } + ], + "behavior": [ + { + "id": "running", + "name": "{i18n:scada.symbol.running}", + "hint": "{i18n:scada.symbol.running-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.running}", + "defaultGetValueSettings": { + "action": "GET_ATTRIBUTE", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": "SHARED_SCOPE", + "key": "running" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": null, + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warning", + "name": "{i18n:scada.symbol.warning}", + "hint": "{i18n:scada.symbol.warning-state-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.warning}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "MAJOR", + "MINOR", + "WARNING", + "INDETERMINATE" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "warningClick", + "name": "{i18n:scada.symbol.warning-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.warning-state}", + "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 + } + }, + { + "id": "critical", + "name": "{i18n:scada.symbol.critical}", + "hint": "{i18n:scada.symbol.critical-state-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.critical}", + "defaultGetValueSettings": { + "action": "GET_ALARM_STATUS", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "scope": null, + "key": "state" + }, + "getTimeSeries": { + "key": "state" + }, + "getAlarmStatus": { + "severityList": [ + "CRITICAL" + ], + "typeList": null + }, + "dataToValue": { + "type": "NONE", + "dataToValueFunction": "/* Should return boolean value */\nreturn data;", + "compareToValue": true + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": null + }, + { + "id": "criticalClick", + "name": "{i18n:scada.symbol.critical-click}", + "hint": "{i18n:scada.symbol.on-click-hint}", + "group": "{i18n:scada.symbol.critical-state}", + "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 + } + }, + { + "id": "criticalAnimation", + "name": "{i18n:scada.symbol.warning-critical-state-animation}", + "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}", + "group": null, + "type": "value", + "valueType": "BOOLEAN", + "trueLabel": null, + "falseLabel": null, + "stateLabel": "{i18n:scada.symbol.animation}", + "defaultGetValueSettings": { + "action": "DO_NOTHING", + "defaultValue": false, + "executeRpc": { + "method": "getState", + "requestTimeout": 5000, + "requestPersistent": false, + "persistentPollingInterval": 1000 + }, + "getAttribute": { + "key": "state", + "scope": null + }, + "getTimeSeries": { + "key": "state" + }, + "dataToValue": { + "type": "NONE", + "compareToValue": true, + "dataToValueFunction": "/* Should return boolean value */\nreturn data;" + } + }, + "defaultSetValueSettings": null, + "defaultWidgetActionSettings": 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": [ + { + "id": "runningColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#FFFFFF", + "required": null, + "subLabel": "{i18n:scada.symbol.running}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "stoppedColor", + "name": "{i18n:scada.symbol.colors}", + "type": "color", + "default": "#666666", + "required": null, + "subLabel": "{i18n:scada.symbol.stopped}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "warningColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#FAA405", + "required": null, + "subLabel": "{i18n:scada.symbol.warning}", + "divider": true, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + }, + { + "id": "criticalColor", + "name": "{i18n:scada.symbol.alarm-colors}", + "type": "color", + "default": "#D12730", + "required": null, + "subLabel": "{i18n:scada.symbol.critical}", + "divider": null, + "fieldSuffix": null, + "disableOnProperty": null, + "rowClass": "", + "fieldClass": "", + "min": null, + "max": null, + "step": null, + "disabled": false, + "visible": true + } + ] +} + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/data/json/system/widget_bundles/buttons.json b/application/src/main/data/json/system/widget_bundles/buttons.json index facca89336..939e583686 100644 --- a/application/src/main/data/json/system/widget_bundles/buttons.json +++ b/application/src/main/data/json/system/widget_bundles/buttons.json @@ -12,6 +12,7 @@ "command_button", "toggle_button", "two_segment_button", + "value_stepper", "power_button" ] } \ No newline at end of file diff --git a/application/src/main/data/json/system/widget_bundles/control_widgets.json b/application/src/main/data/json/system/widget_bundles/control_widgets.json index c3dc219f5b..71a25df0c2 100644 --- a/application/src/main/data/json/system/widget_bundles/control_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/control_widgets.json @@ -12,6 +12,7 @@ "command_button", "toggle_button", "two_segment_button", + "value_stepper", "power_button", "slider", "control_widgets.switch_control", diff --git a/application/src/main/data/json/system/widget_bundles/general_high_performance_scada_symbols.json b/application/src/main/data/json/system/widget_bundles/general_high_performance_scada_symbols.json index 5083384432..5fbbd8b066 100644 --- a/application/src/main/data/json/system/widget_bundles/general_high_performance_scada_symbols.json +++ b/application/src/main/data/json/system/widget_bundles/general_high_performance_scada_symbols.json @@ -28,6 +28,12 @@ "hp_left_tee_connector", "hp_top_tee_connector", "hp_drawwork", - "hp_crane" + "hp_crane", + "hp_hook", + "hp_consumers", + "hp_house", + "hp_apartments", + "hp_manufacture", + "hp_electrical_engine" ] } \ No newline at end of file diff --git a/application/src/main/data/json/system/widget_bundles/high_performance_scada_energy_system.json b/application/src/main/data/json/system/widget_bundles/high_performance_scada_energy_system.json new file mode 100644 index 0000000000..a06ccc0819 --- /dev/null +++ b/application/src/main/data/json/system/widget_bundles/high_performance_scada_energy_system.json @@ -0,0 +1,48 @@ +{ + "widgetsBundle": { + "alias": "high_performance_scada_energy_system", + "title": "High-performance SCADA energy system", + "image": "tb-image:aHBfc2NhZGFfZW5lcmd5X3N5c3RlbV9idW5kbGVfaW1hZ2UucG5n:IkhpZ2gtcGVyZm9ybWFuY2UgU0NBREEgZW5lcmd5IHN5c3RlbSIgc3lzdGVtIGJ1bmRsZSBpbWFnZQ==:SU1BR0U=;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAAAllBMVEXe3t7b29vf398AAADf39/e3t7e3t7////Gxsbj4+OpqanFxcW5ubmtra2xsbGVlZWlpaWhoaHS0tLx8fG2trbU1NTBwcGvr6+MjIybm5twcHC+vr6jo6OYmJizs7Orq6vExMSZmZnCwsKdnZ2ampqRkZE3Nze7u7t+fn6EhIRjY2NVVVVsbGx5eXmJiYlEREQvLy8aGhprAy3xAAAABnRSTlPvIK8Av79l/pT7AAAJyElEQVR42uzVXW/aMBiGYWin57VjmyRmNGFLKOWjQACt/P8/t7yJQVMPrE62KkBcR88ZuokTD56HPwa4dU/D58Hw9jNabcYT7sIdHKve4F5C8Ai5No+Qa/MIuTaPkGvzCLk2QSHKIB6rFAIEhayniCc9fVx2c2ou++OUwi88ZGEtYrHbfZOf9/59r9w2f963Fj7hIUaOK0SSWyKSS8s7461rsOmk3TMNj/CQKVGBOJSiTsJNkpjcACjHxOYWHsEhCyJhEYWW1FkDeKNeDcD0c2LgERpi+McrxFFqIYQeaQCqnshWrQAUL7zFGh7BIVNq5YjDJm1Jnfd7lySrnQLT23aX8AgPWbjTG0VFTHKJyohlXLKZ8xy/wiMwxB3rsUEwd0yZ+ecdyQCs+yk1PAJDMuokiMFmxMZl/9W6PJ2EOlrBIzAkp84IUahym6yOG7D0WCarXQE2Oq6Scm3gERoyJxbrA5wJfiCV4l3zljVPu5R8IW7gERZy+fKPC0SgJu4e+XS0Ss+/FSkkI2eKQJ8vxOx7L8RXcjaI4W3GF2KhAKhU8CXYbTHjrZfwCAz5Tc4LohA/pVy7nbZRqds7IVYaHqEhc3ImFgGKkZM1TVO6XR4Oh9rtX+2uRo4BYodYQY7UCCDofwggdoiRdJYDuOEQYuFXYjEqk68qqwKIHZJHCoGQ9FVSgcUNSe8lxNBFBXarIVrSmQG71ZC/1Jpdc6sgEIbv3AXRIB9VE2OStmN6mpnT8/9/3RlSbGTxzmFC9yYTfV14wrIsRLQk/f4ikHDhM+ANVSD6BSB/VLg39SbDJKCyBxFst1o0tkEOsEP2ID20q/nXEFH2IK+Aai1tVVSUOYgrrnYrsx0VEQ2Zg3A6HVq/rlNRkzlI7U/jouMgutsyKmuQEulpHMYHdKW9X8kapIl//hHcIK2Isgbp4zWDRznr9C3KGuQAzkSQorSf60R0zhnEb9G1WF77K7AvYxETm0HYJ0sEUuko2V6lu/Zax6JhM8j+a58IZIjqkav13uuoaum3gtgJJkwDYqJ6ZARvZ0VFm0Nr4oC3NCBv3teBhJGzHS2I240g3MXVjScBEXTzcVm0S0WHbSBsYsAY+6fTgJDJLtOBdB2wzn0kD62yMYdew2z94dSIbaFFB2T/9Ql6YglA5kh6UxUf7X0cwNu9J/b1VCk5izaB7J1nt470t2TpVwup5pGwc/q1P0Q73ymzBYRPrqF7KzdMMEcYAHK79CpHDaDNUcHDEHFraE0ffLaPKUGJYizqwGldnAQKcyyuNrjO0YotIN3+YX2CEbno0OelKCofRgMLb/WiyLZolBYijqKZy5GakbudyhOkbHXM4Yt2UayQgCxzBMER1jiK2oOskZzb/EDqgvqTi7Kx8SJLRcfcQI60oWLwd16W5Qi+RCR5gYQcwYuSPKgiSxkNXE4glEO3D2cYvi6gBNXKfEAoB17oDv1loZaGkuQCQji0sG/0Pyu+BGFIHjjmASKDNrTr+VmRHmB4GoSchTM+B5CWLSisQOKrYvQwnvtiSy9n/PNBSvUYC/7hO4enWswgofNyd+Je33Hxw2KvzwYpR991I6wG9L44ADNVXYYgqhGSwY8I70/5b+fyuSBKAjDRCaPBmX4HZ8a3ik1dDt/OK7VrEVZEWvB3YQFG9VQQ+b+9c1tuGwTCcI+wIJCEhK26suW2aafpRfv+j9ehIgtZuQk20QRn8t9kRP7Y+5nDamUUDfbzr1AzWT2oDTQ65G+YQfiPYPqmBxD3TU0jh9tnHlo74CgGzHLRH5gKqUJ6xGBSTM6mLxwl1NMNrc1FPdLxexzsZq6iFONegCDBdJxPWgBNzvDgwrVnG5GqDavPBemkIBxM+ewHd4Rq/lEtTYREHLvn2njmJnJQB/Nq64sPtcWhFf34AswXjcd/JkvqrGcAoSey3xR+iauiX4sdjrBwTjwG00RIngGk2vJIdcUwRuMNNecCZDTC9pFJn/w0/kfSwjlqk0Eoh5Cdb67ji7o7AA4IEkwu+AFNgpCsKQpCxsQ0YLvDq9FkcdUCrtoKTTEtM1MGSS4Ijoj5KiJDWceFpmiyyz4Eacm2pzrq3WlVEgrSIQeM97deWayiPGzlffFkBm8Kqs3msRm/AgherIaBvOsco41NNk6I+46aUFVtgKNpJVGQnYOo5bB8xw5wZZWzyUfXyhF6nz0ATm986IysnWnHVhIF+dMcW92xpXxhC7EJws0KejoOf5xJeNMJdXq6GRqWqfydDyDIWRvAcgOdciZYcyrkg2hBzqMdmZOKTb0zFQ5CN2doDwJdbLoSkF5HDf2ySDBzzchWUz6IX3JjECzbqKlskNpvckA1WAJSk2HrKR/kK90c5IKmw61N3BxUGQVBzdmTSis4XApiKUjLvczS9JgU+X4rHQE/RMgDORAQMtwkmlJeDCVU6HhtHtbXLhPkBrNfFAztgGPiBwycT7XXPl7nJH9UQw4IjgZJyHzcxJQCgn+lRQQCKRe38kA0TePqBIjhTscUkPoUCKSA6DyQAUEiMjrcRm9KAWmn7SwAsFsvmSA7HjJAfIyGkDntqEmngPzu+cXqf2eA1HRL2ci9FF3JhhQQyTMkM0C2y4TopalpLBrEcqeJBO00UFN7BSBb0kA6AGZT0SDw34TYLkwlg3RAJzbQ/zCAaUCWDOJvBAVCFncAVr9QNIjAcjAiIx2gPEhVMIgWpBxUgl7qx+pXFA1CT7h1jyBVMF0NSK/jBhq38Q26YJA9TeMmAqGmkkFqWg7uA4imJlMwyFdaDtYcpWn1uy8YxD5w20WzMF0ByCFuoHFLNKWCbCRKcREdrAZyQ9O4XILg/SNtMogA1Ib30cFqIIqm8SNHHRamgoeWpuWg4ihLl+RjwSADBTEcJWn1q9LnyGfUrTtArQWCMZq4gcY9XlFCHEMDStEleSgYZEtXqImjDDWNySC9QPXxgVg9j0xx0DRue/byC59Qkm8+Ba0Oso3zCI0b7kwFDy2gSy1w1LQwlQuCFbqKG0gHAKyYEDfKKlB9Jkio0CEOmsQNAk1PDvJ9RpAiF0TgjtWogcStEKR6ahDZI1AeiBakHNQxSEfKeJEAouQZ+o7939+6Y3U5CP26T5N9xdiWBGL5eUKOsDtXZYL0OjTQuA0tfk8LnelC8DAk9WUge1rXNjGIWpoeVqXhLH2KmMBJmeoCkP1u90Pe6dfOCRtCGzG17FE1QogmeYrSpf0CkJZfoG0CCNb7yVUE/1JdN0jV4s3nhYIYlkiigHMx/mQ5IKa5QIYlvTBLVqUbxU7p9Rk9heoVpDS9gpSmV5DS9ApSml7Mg3XffGAvQu9ezkO037+9/sdov/n49v1fKIFpFaO3gooAAAAASUVORK5CYII=", + "scada": true, + "description": "Bundle with high-performance SCADA symbols for energy system", + "order": 9410, + "name": "High-performance SCADA energy system" + }, + "widgetTypeFqns": [ + "hp_solar_panel", + "hp_stand_solar_panel", + "hp_wind_turbine", + "hp_wind_turbine_cluster", + "hp_fuel_generator", + "hp_industrial_fuel_generator", + "hp_circuit_breaker2", + "hp_horizontal_circuit_breaker", + "hp_voltage_relay", + "hp_3_phase_voltage_relay", + "hp_voltage_stabilizer", + "hp_energy_meter", + "hp_two_rate_energy_meter", + "hp_three_rate_energy_meter", + "hp_four_rate_energy_meter", + "hp_electrical_distribution_board", + "hp_power_socket", + "hp_single_key_switch", + "hp_two_key_switch", + "hp_top_light_bulb", + "hp_bottom_light_bulb", + "hp_battery", + "hp_inverter", + "hp_large_inverter", + "hp_horizontal_energy_systems_controller", + "hp_vertical_energy_systems_controller", + "hp_small_power_transformer", + "hp_power_transformer", + "hp_low_voltage_transformer_tower", + "hp_low_voltage_tower", + "hp_high_voltage_tower", + "hp_consumers", + "hp_house", + "hp_apartments", + "hp_manufacture" + ] +} \ No newline at end of file diff --git a/application/src/main/data/json/system/widget_bundles/high_performance_scada_fluid_system.json b/application/src/main/data/json/system/widget_bundles/high_performance_scada_fluid_system.json index 1e41b22446..29a0571dfa 100644 --- a/application/src/main/data/json/system/widget_bundles/high_performance_scada_fluid_system.json +++ b/application/src/main/data/json/system/widget_bundles/high_performance_scada_fluid_system.json @@ -5,7 +5,7 @@ "image": "tb-image:aHBfc2NhZGFfZmx1aWRfc3lzdGVtX2J1bmRsZV9pbWFnZS5wbmc=:IkhpZ2gtcGVyZm9ybWFuY2UgU0NBREEgZmx1aWQgc3lzdGVtIiBzeXN0ZW0gYnVuZGxlIGltYWdl:SU1BR0U=;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAABNVBMVEXe3t7f39/X19cAAADf39/e3t7f39/e3t7e3t7////I3/fr6+vHx8esrKzj4+OcrsHT0tKVlZWvr6/Kysrx8fF0dHT9/f24uLjBwcHc29v39/etra27u7ubm5vt7e2/v7/Dw8OysrKqqqq+1ev6+vrE2/Lh4eHV1dWoqKiQkJCmpqa1yt+2trbu7e2YmJi/1eyurq7B2O9ycXFlZWXT09Ojo6PMzMzIyMjm5eWBgYGenp78/Py0tLSTk5OLi4uwxdq6z+Wous6ioqKGhoZjY2OgoKBoaGheXl5WVlasv9SorrR/fn7R0dGlpaVSUlKhrr13d3evuseTpLWWoq+fn59sbGw+Pj6rv9Tv8veitci4uryAk7vf5O7P1+WQocRgeasvLy8hISG/yd2frsx/k7tvhrNAXZmZOz+XAAAACHRSTlPvICAAv7Cvv9cI9yEAAAzySURBVHja7JttT9pQGIaNe8lTejg9dDRqJEElwFxNN1lIZNko3RrRuoAvUaOf/P+/YkecPq0v0PY5lZZ48dUvV06vm54Ylt4tv1+CovNx+d3ScvE1JFLjAywE8R6rNcg9SzNEama36/R/raxLVmazsfrTrMFcmCYiNrdAb7VX6ht6u91e0+MhdkwGBNSLdDd0ZK/V0mOzYwIBxSLiJ+hhEc71+EBdAAGVIuKzTuIzwUSpyKoeZa/FE5oAAXUijo609D0UiQ+hE3UiTOhIvf5Jb0kSimwIoEIX2dTDIp9wfpOwCXGpHHyfwqiSXgSHNzy/mR1J5RtMYT29SE1HEseOmHMX+aIroT53kdVnToTvJRbZmbtINJE1+bl9tAooYpTCGIZbakhKSTFyJnLlHZZs2x4UXmRC8+vXRRHZXQwRyWKILMCJNO2GvRAiJds7VDy/XZqImVLEHaie35s/LCrSX59CPyrChhe5mV+LW1ZYhVUi+H4lAgNEHPWqVm5itzSN+87LybwcRd9nmmblZn4tTbJ1YCYVMc9XNEmuTuSW7WOWRIQNt6saRWTQcKXIblO5iMatIzZbBOPQNJLIlYHzq1REwg+ceCLObRxEEdt2cX7VikxSqc0WMc+3NMTKz/xqYbb32XQRti/j+E8uY9cwlSki4qjDtRC5m98wbUzlsYjTmcRRiBO5S4U9J2IOMQ6yiDvwshfRqg+poAg7xjhUiGQ4v09TQRFx5GMcKkQuB3amIgg7cFCkG42jAPMbSWXI7kTM4Q/tnkLFfg+XqVQq4tE3R2HmNwzvXYwmcRT7RKSI3+v1MhFxB/YriOAKi8lLCVcu0jyV8zuwbfs1RH4MzfvLR1n5iTQbrza/nS7+kTPKZn53s3u0MA4BYXoWL2DsVbz4Rt7fiza/ZRnHM7BRuVAnwjoOPAJTaSubX9vNVoR3MI6nCEyFKpLp/OL7O/I0FTUijUGG81s+N2EmtVE5540w34FYOCOeY5FqD+OYBZOp5HR+q9tDBnHAi28uT6SGccTFHJVJV91LT70I8/uQAsfn6UUOPeXzi3EkhVkWTytiN1XP7wXGkRz25+a1G/HgBUwg0Y0tMi49It3/R04gW2aLnJSUcAY06CLf1Dxa10CDLiK8yPy6V6lEPCBCF4Egslqul2p+AyBCF4keSVN+Usyvy4AKXQQCeiMVoEMXgbOnIjne3iki4ow4vX+BAF0EEScYRTPpF2JjTPWgiyDB+OHSnvDR8n4DGboIwoKxO9nfRPPbOAsIx6FWBLkOxieeYRinEmM243EQXMNcWIgfuL6J5JE3kX/tneeu0zAUgEEsH8mx3MY0o0naRIiEqK0Q6oDS3h8gJCRA4gcgJJaY7/8I2Bk9GaVJKKOMD6Gbyy3H/myfeKSFU+O/yKnxj4nw/uzBIjY0yY07Dx9eXWuHwHOn1cigPwt/3lWEznyNQRFhrNchfBONZMyn8BOZrjqJPPEp7CNcU9Ek4sNPZdRehJ+5Ar4J1dhhEQo/Fae1iB8LOAilv1OEthSh9xk0wW6wkxd5EkEbAu3ERbCpG2BrccIifCugNWvWKEL38QtE+Ay6ELAGkRXsQYQ/UQQ9jjCpi9D9FfnpIlvoSiBOUWTfJCiCReDOegs36E8Y1Fn/aBERjkeSMRXfLfKE1YJGwU1Odty8Y9Zf4vxIEToynbxAZ9MPv0uEbqp1dM84qdA7q6pQ2kbk7ds3r941icwnlfKk13eIONWXu0nYobudbYLI37oPku/9auy1aBZ5+eHdS3h1WOR6psEN3/dHw8wp9rqKzBiUmDkq6MRl4N2GBBb0VAh6j1WyqFkE3nyCj68PivRp0prBZumAIlxqwySW3lEkKtfuPpcaQVrkLpTJHBXELZsYorlHvrx6/flQjogg0RipUHwKKSvfT2rGuoj4rBT3hgqbu9lzSOEAgVs3oUcnO1NRaV8ANlz6NaZqeLEOIuVM36raziHHyr6ogKxuEoruInUPf1eeZUOCqX4UYys3idQzJOayro91LMgqtJTo8erUuT5SZKIG0AQkaCDh6RSgfijaiiygQECTNvfQhKfxsb94/ANF/KSqnlkRsTzYmTgtRSgtzR+E9JjKDRUQ+2JqFTLoJgNE0GNEGE3TAE0sq9huIiaE83YipfaN08kxM8HGMSBjeuMmIfehgEYVd2oiId2xWtEcBiVcQugU6g3HIWOywho2iCwAYT4hdwBKJrYuf1tYTF92CS3VpQSFDpgqm3fFoYju5e3G1bGJ2UaEG+XAONI8fRcZPTwQi3K+U7pfhF2XvyTXQwbXc+ZiUDnc8fcUhwPATAZJ3EakzwCRCXADv7PSaX2FGTJnAKpLFgKQJ3tFBtg188G4cA1ISIutfZtnItgh6mKCWXJQxAWE3ZTfr1Y611M7007Hq14qit4kfALIep/IoCBy/S6KjAdooppkONRNa5pVe5rlZC5nKiHhELJqIfKsfE9/wLIq65ZKjzS+hR5ZVtwDZNbUI3URHFkaqAzUzUTCSEXyDsk2DiuVx80ii/Kk7gKOWWt6ezq7f3b/eRpQqqXcI+SsqL+3R+4mF04ick2KCMaYkCJ3YYD7GRxZti7DexaEdNTPytFZ5k4IZ00imEjY1IhlPTWXkhtMleQVat4DRPu2yNX3N3IR9uL9C6ZEsEsoITzEOJ6hjx2RnwbYppf7qkxqFgkAmWVeaGYuN+tgubwDlr4rzzyjZMhgh0P3iVyTF/Tq1athJgLsIVPNe21QHFlDKOKNpICdDDEsz7YiQkbNIhogvcwLRZb9W7duPVk+mHhZUH1uAXMIDWv333qP5CiRDCUyqN98cQVq3LolNYYGyy24afuEOEeKnC23UuTRcnJPl8xlSmY3N0qbRK7tEykPLT1taYTC9JaEwcaTWCtT1201lI8SsT2+4jeWaxl4uwywA9qK3G0WCasiI/CUiAWxxbk1tUHSWsQAZJFuFr2VPuf6bds2by2Xs0f3zWW2uMKaDxuH1mBOM5zJtcfjnMe1oZXdf2V36w8A4lu3eri07CISlyd2Vw6hbHNzW7fZ2VKhjaGIIacb0XTXGly7Zmdcu3b32u5yUBLpA9N1k6czoe456eqXQolRq2SfAfIA76vZ7ZY9DZabO2fGFMod5wISVETQJOPu3bvXdqCHmiDo0MrLM+QVc0DiLKGIuCnDNosMAYlwFTU1IcHUYip0sOa8tCZ5AIj/DZHB3UGKvM5c7qo/Km1GJvnqx0yL1YaU+oybhZabq+mmWSQqD35OS6sR3bO8dKniWTqfZrmuykee1UWQpNpKJQcK4H1Vz8ozwEzXvNzUp7gic9osUQQgZ8mmCT2mLN1XWdM8J7m+imQ3sqa1VkVnv8hKVlFkq+p8tWvmu0RbupiWZytdret+xE/3sVMd8gYCj+3WwbiLXAMSOs0iWbdABc4J2aBHUu4K18GS2xbv4c7qoAjxK+v4e+iRDSsF6m4ooQEgwRE7xE1y17QK7QYcKg3nYhUPi7hQ4J468tMho3AOYJvl3TDy7Mit7mbXbrqHxfV3L+Etd4ikTwFhMrO2Aj0kenknuiblDmGLI0TUMHUmUOgQmNupiYHt5rM2IpWF24bjIYkBeWQ0iWllqT8mx4hwjidwlpcYlBtObXRNaCfiisqJKe9hYDxBkaGVR/XM9P5RIuAQvAUXv2RHXSFGbBbhQfUslm9FIaKBk/2aVk4rgNLjRESc/51pLmLnJoa9wmPsBpF6uuNJNa+cKwNTzxucMexN9e4imAQkYjiEbX0nqWao1RRai/CgbnLzbOSBAg+wXRV3MYEiGj1GBE10E0wsLuXxSHlc7/TESpRD3+BE/bNEIaRwO3uoiM8bcL18tAiwmEjcZ3hSgA8V/Wm3Z4g+lIkfEInj3otoyBjdLtybSSW3AkpE9FgRhUh3/X6fpuFNMR/FPBkqrOvD0GrxTMVWcOfm7jKovWpGjhFBTJ8k3BxNJuNJ5PDsIXX35+xxTT281+ME4cMtBQQf0h0tgioIPlTsKoIbDEQY99wHQ2M8HvbkGNsT1SXHiyCm5nCSwZ1gggV2EuEx7IWFNGSwl4g2iHSGjkYOpY4zDo94Uw2NoAN1j9N54xmNunqcqAihbjePkxUhfMugHWJGyQmLEOI2VQOn/tMWIdQV0EjkEHLqIqQXOE2h1i4/EGADP5XHLUVcIceNBg3vjd8eEIkF/ESE3yyC7whkgcH2h9Eo2y30u+fZ8cw3pJUIjbDhjWp9Qk2jIr/uH/wETQPumO7FeNJvgLf93/dKFTccQ9ZdoRmBpoVQYEPJd4J7pTrXV6SBBpF4lBBpddY3JFqdaJRwg3QEPZpNuoss4HsZk248mUwieoBgMknT+dRFaNvP7PwFIph93UUi7Xs5I92IaCMRac25v+UTlecukr+CS+fO/xVdIjUunL9M/nTOXTl/4St0s1h4GZ5DIgAAAABJRU5ErkJggg==", "scada": true, "description": "Bundle with high-performance SCADA symbols for fluid system", - "order": 9350, + "order": 9415, "name": "High-performance SCADA fluid system" }, "widgetTypeFqns": [ diff --git a/application/src/main/data/json/system/widget_bundles/high_performance_scada_oil_gas.json b/application/src/main/data/json/system/widget_bundles/high_performance_scada_oil_gas.json index 87b778be21..21ee5d7f05 100644 --- a/application/src/main/data/json/system/widget_bundles/high_performance_scada_oil_gas.json +++ b/application/src/main/data/json/system/widget_bundles/high_performance_scada_oil_gas.json @@ -15,6 +15,20 @@ "hp_preventer", "hp_drill", "hp_drilling_line", - "hp_platform" + "hp_platform", + "hp_electrical_engine", + "hp_oil_pump", + "hp_gas_wellhead", + "hp_gas_preventer", + "hp_heat_exchanger", + "hp_turbine", + "hp_large_vertical_separator", + "hp_large_vertical_separator_with_connector", + "hp_large_horizontal_separator", + "hp_large_horizontal_separator_with_connector", + "hp_small_vertical_separator", + "hp_small_vertical_separator_with_connector", + "hp_small_horizontal_separator", + "hp_small_horizontal_separator_with_connector" ] } \ No newline at end of file diff --git a/application/src/main/data/json/system/widget_types/update_device_attribute.json b/application/src/main/data/json/system/widget_types/update_device_attribute.json index 92111daf54..763ba93bc9 100644 --- a/application/src/main/data/json/system/widget_types/update_device_attribute.json +++ b/application/src/main/data/json/system/widget_types/update_device_attribute.json @@ -9,7 +9,7 @@ "sizeX": 4, "sizeY": 2, "resources": [], - "templateHtml": "
\n
\n {{title}}\n
\n
\n
\n \n
\n
\n
\n {{ error }}\n
\n
", + "templateHtml": "
\n
\n {{title}}\n
\n
\n
\n \n
\n
\n
\n {{ error }}\n
\n
", "templateCss": ".tb-rpc-button {\n width: 100%;\n height: 100%;\n}\n\n.tb-rpc-button .title-container {\n font-weight: 500;\n white-space: nowrap;\n margin: 10px 0;\n}\n\n.tb-rpc-button .button-container div{\n min-width: 80%\n}\n\n.tb-rpc-button .button-container .mat-mdc-button{\n width: 100%;\n margin: 0;\n}\n\n.tb-rpc-button .error-container {\n position: absolute;\n top: 2%;\n right: 0;\n left: 0;\n z-index: 4;\n height: 14px;\n}\n\n.tb-rpc-button .error-container .button-error {\n color: #ff3315;\n white-space: nowrap;\n}", "controllerScript": "self.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges();\n });\n};\n\nfunction init() {\n self.ctx.$scope.buttonLable = self.ctx.settings.buttonText;\n self.ctx.$scope.showTitle = self.ctx.settings.title &&\n self.ctx.settings.title.length ? true : false;\n self.ctx.$scope.title = self.ctx.settings.title;\n self.ctx.$scope.styleButton = self.ctx.settings.styleButton;\n let entityAttributeType = self.ctx.settings.entityAttributeType;\n let entityParameters = JSON.parse(self.ctx.settings.entityParameters);\n\n if (self.ctx.settings.styleButton.isPrimary ===\n false) {\n self.ctx.$scope.customStyle = {\n 'background-color': self.ctx.$scope.styleButton\n .bgColor,\n 'color': self.ctx.$scope.styleButton.textColor\n };\n }\n\n let attributeService = self.ctx.$scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n\n self.ctx.$scope.sendUpdate = function() {\n let attributes = [];\n for (let key in entityParameters) {\n attributes.push({\n \"key\": key,\n \"value\": entityParameters[key]\n });\n }\n \n let entityId = {\n entityType: \"DEVICE\",\n id: self.ctx.defaultSubscription.targetDeviceId\n };\n attributeService.saveEntityAttributes(entityId,\n entityAttributeType, attributes).subscribe(\n function success() {\n self.ctx.$scope.error = \"\";\n self.ctx.detectChanges();\n },\n function fail(rejection) {\n if (self.ctx.settings.showError) {\n self.ctx.$scope.error =\n rejection.status + \": \" +\n rejection.statusText;\n self.ctx.detectChanges();\n }\n }\n\n );\n };\n}\n", "settingsSchema": "", diff --git a/application/src/main/data/json/system/widget_types/value_stepper.json b/application/src/main/data/json/system/widget_types/value_stepper.json new file mode 100644 index 0000000000..dea2406539 --- /dev/null +++ b/application/src/main/data/json/system/widget_types/value_stepper.json @@ -0,0 +1,51 @@ +{ + "fqn": "value_stepper", + "name": "Value stepper", + "deprecated": false, + "image": "tb-image;/api/images/system/value-stepper-widget.svg", + "description": "Allows users to click the buttons to send commands to devices or update attributes/time series data. Configurable settings let users define how to retrieve the initial state and specify actions for each button.", + "descriptor": { + "type": "rpc", + "sizeX": 3.5, + "sizeY": 2, + "resources": [], + "templateHtml": "\n", + "templateCss": "", + "controllerScript": "self.onInit = function() {\n self.ctx.$scope.actionWidget.onInit();\n}\n\nself.typeParameters = function() {\n return {\n previewWidth: '230px',\n previewHeight: '110px',\n embedTitlePanel: true,\n displayRpcMessageToast: false\n };\n};\n\nself.onDestroy = function() {\n}\n", + "dataKeySettingsForm": [], + "settingsDirective": "tb-value-stepper-widget-settings", + "hasBasicMode": true, + "basicModeDirective": "tb-value-stepper-basic-config", + "defaultConfig": "{\"showTitle\":true,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"initialState\":{\"action\":\"EXECUTE_RPC\",\"defaultValue\":0,\"executeRpc\":{\"method\":\"getState\",\"requestTimeout\":5000,\"requestPersistent\":false,\"persistentPollingInterval\":1000},\"getAttribute\":{\"key\":\"state\",\"scope\":null},\"getTimeSeries\":{\"key\":\"state\"},\"getAlarmStatus\":{\"severityList\":null,\"typeList\":null},\"dataToValue\":{\"type\":\"NONE\",\"compareToValue\":true,\"dataToValueFunction\":\"/* Should return integer value */\\nreturn data;\"}},\"disabledState\":{\"action\":\"DO_NOTHING\",\"defaultValue\":false,\"getAttribute\":{\"key\":\"state\",\"scope\":null},\"getTimeSeries\":{\"key\":\"state\"},\"getAlarmStatus\":{\"severityList\":null,\"typeList\":null},\"dataToValue\":{\"type\":\"NONE\",\"compareToValue\":true,\"dataToValueFunction\":\"/* Should return boolean value */\\nreturn data;\"}},\"leftButtonClick\":{\"action\":\"EXECUTE_RPC\",\"executeRpc\":{\"method\":\"setState\",\"requestTimeout\":5000,\"requestPersistent\":false,\"persistentPollingInterval\":1000},\"setAttribute\":{\"key\":\"state\",\"scope\":\"SERVER_SCOPE\"},\"putTimeSeries\":{\"key\":\"state\"},\"valueToData\":{\"type\":\"VALUE\",\"constantValue\":0,\"valueToDataFunction\":\"/* Convert input integer value to RPC parameters or attribute/time-series value */\\nreturn value;\"}},\"rightButtonClick\":{\"action\":\"EXECUTE_RPC\",\"executeRpc\":{\"method\":\"setState\",\"requestTimeout\":5000,\"requestPersistent\":false,\"persistentPollingInterval\":1000},\"setAttribute\":{\"key\":\"state\",\"scope\":\"SERVER_SCOPE\"},\"putTimeSeries\":{\"key\":\"state\"},\"valueToData\":{\"type\":\"VALUE\",\"constantValue\":0,\"valueToDataFunction\":\"/* Convert input integer value to RPC parameters or attribute/time-series value */\\nreturn value;\"}},\"appearance\":{\"type\":\"simplified\",\"autoScale\":true,\"minValueRange\":-100,\"maxValueRange\":100,\"valueStep\":0.5,\"showValueBox\":true,\"valueUnits\":\"\",\"valueDecimals\":1,\"valueFont\":{\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"size\":16,\"sizeUnit\":\"px\",\"lineHeight\":\"24px\"},\"valueColor\":\"#000\",\"valueBoxBackground\":\"rgba(0, 0, 0, 0.04)\",\"showBorder\":true,\"borderWidth\":1,\"borderColor\":\"#305680\"},\"buttonAppearance\":{\"leftButton\":{\"showButton\":true,\"icon\":\"arrow_back_ios_new\",\"iconSize\":24,\"iconSizeUnit\":\"px\",\"mainColorOn\":\"#3F52DD\",\"backgroundColorOn\":\"#FFFFFF\",\"mainColorDisabled\":\"rgba(0,0,0,0.12)\",\"backgroundColorDisabled\":\"#FFFFFF\",\"customStyle\":{\"enabled\":null,\"hovered\":null,\"pressed\":null,\"activated\":null,\"disabled\":null}},\"rightButton\":{\"showButton\":true,\"icon\":\"arrow_forward_ios\",\"iconSize\":24,\"iconSizeUnit\":\"px\",\"mainColorOn\":\"#3F52DD\",\"backgroundColorOn\":\"#FFFFFF\",\"mainColorDisabled\":\"rgba(0,0,0,0.12)\",\"backgroundColorDisabled\":\"#FFFFFF\",\"customStyle\":{\"enabled\":null,\"hovered\":null,\"pressed\":null,\"activated\":null,\"disabled\":null}}},\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}},\"padding\":\"12px\"},\"title\":\"Value stepper\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"actions\":{},\"widgetCss\":\"\",\"noDataDisplayMessage\":\"\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":null,\"weight\":\"500\",\"style\":null,\"lineHeight\":\"24px\"},\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"pageSize\":1024,\"titleIcon\":\"\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"14px\",\"configMode\":\"basic\",\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"datasources\":null,\"borderRadius\":null}" + }, + "resources": [ + { + "link": "/api/images/system/value-stepper-widget.svg", + "title": "\"Value stepper\" system widget image", + "type": "IMAGE", + "subType": "IMAGE", + "fileName": "value-stepper-widget.svg", + "publicResourceKey": "s0UKoqbiMCcKVn0pD55XZzPUR89XlXAO", + "mediaType": "image/svg+xml", + "data": "PHN2ZyB3aWR0aD0iMjE0IiBoZWlnaHQ9Ijc2IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxnIGZpbHRlcj0idXJsKCNhKSI+PHJlY3QgeD0iOC41IiB5PSI0LjUiIHdpZHRoPSIxOTciIGhlaWdodD0iNTkiIHJ4PSI0IiBmaWxsPSIjZmZmIiBzaGFwZS1yZW5kZXJpbmc9ImNyaXNwRWRnZXMiLz48cmVjdCB4PSI5IiB5PSI1IiB3aWR0aD0iMTk2IiBoZWlnaHQ9IjU4IiByeD0iMy41IiBzdHJva2U9IiMzMDU2ODAiIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyIvPjxyZWN0IHg9IjIwLjUiIHk9IjE4IiB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHJ4PSIxNiIgZmlsbD0iIzMwNTY4MCIvPjxwYXRoIGQ9Im0zOC41IDQwIDEuNDEtMS40MUwzNS4zMyAzNGw0LjU4LTQuNTlMMzguNSAyOGwtNiA2IDYgNloiIGZpbGw9IiNmZmYiLz48cmVjdCB4PSI2NSIgeT0iMTguNSIgd2lkdGg9Ijg0IiBoZWlnaHQ9IjMxIiByeD0iMy41IiBmaWxsPSIjMzA1NjgwIiBmaWxsLW9wYWNpdHk9Ii4wNCIvPjxyZWN0IHg9IjY1IiB5PSIxOC41IiB3aWR0aD0iODQiIGhlaWdodD0iMzEiIHJ4PSIzLjUiIHN0cm9rZT0iIzMwNTY4MCIvPjxwYXRoIGQ9Ik04OC41OSAzNy41VjM5aC03LjYzdi0xLjI5bDMuNy00LjA0Yy40MS0uNDYuNzMtLjg1Ljk3LTEuMTkuMjMtLjMzLjQtLjYzLjQ5LS45YTIuMyAyLjMgMCAwIDAtLjA2LTEuNzNjLS4xMy0uMjctLjMyLS41LS41OC0uNjVhMS43IDEuNyAwIDAgMC0uOTMtLjI0Yy0uNDIgMC0uNzcuMS0xLjA2LjI3LS4yOC4xOS0uNS40NC0uNjUuNzZhMi42IDIuNiAwIDAgMC0uMjIgMS4xaC0xLjg4YzAtLjY3LjE1LTEuMjcuNDYtMS44Mi4zLS41NS43My0uOTkgMS4zLTEuM2E0LjEgNC4xIDAgMCAxIDIuMDgtLjVjLjc2IDAgMS40LjEzIDEuOTQuMzguNTMuMjYuOTMuNjIgMS4yIDEuMDlhMy4zNiAzLjM2IDAgMCAxIC4yNSAyLjcyIDUgNSAwIDAgMS0uNDkgMS4wNCA5IDkgMCAwIDEtLjc0IDEuMDRjLS4yOC4zNS0uNi43LS45NCAxLjA1bC0yLjQ2IDIuNzFoNS4yNVptOS4yNy05Ljg4djEuMDRMOTMuMyAzOWgtMS45OGw0LjU0LTkuODhoLTUuOXYtMS41aDcuODlabTEuOTggMTAuNDRjMC0uMjkuMS0uNTMuMy0uNzMuMi0uMi40Ni0uMy44LS4zcy42LjEuOC4zYy4yLjIuMy40NC4zLjczYTEgMSAwIDAgMS0uMy43NGMtLjIuMi0uNDYuMy0uOC4zcy0uNi0uMS0uOC0uM2ExIDEgMCAwIDEtLjMtLjc0Wm02LjQ5LTQuMzUtMS41LS4zNy42MS01LjcyaDYuMTR2MS42SDEwN2wtLjMyIDIuNzlhMy42NyAzLjY3IDAgMCAxIDEuODEtLjQ2Yy41NCAwIDEuMDIuMDkgMS40NC4yNi40Mi4xNy43OS40MyAxLjA4Ljc2LjMuMzMuNTMuNzMuNjggMS4yYTUgNSAwIDAgMSAwIDMuMDcgMy4xNyAzLjE3IDAgMCAxLTEuODUgMi4wM2MtLjQ2LjE5LTEuMDEuMjktMS42NS4yOWE0LjYgNC42IDAgMCAxLTEuMzYtLjIgMy43MyAzLjczIDAgMCAxLTEuMTctLjYyIDMuMTQgMy4xNCAwIDAgMS0xLjE5LTIuNDJoMS44NWMuMDUuMzcuMTUuNjkuMy45NS4xNi4yNS4zOC40NS42NC41OGEyIDIgMCAwIDAgLjkyLjJjLjMyIDAgLjYtLjA1LjgzLS4xNi4yMy0uMTEuNDItLjI3LjU3LS40OC4xNS0uMjIuMjctLjQ3LjM0LS43NWEzLjYyIDMuNjIgMCAwIDAtLjAyLTEuODcgMS45OCAxLjk4IDAgMCAwLS4zOC0uNzJjLS4xNy0uMi0uMzgtLjM2LS42My0uNDdhMi4xMyAyLjEzIDAgMCAwLS44OC0uMTdjLS40NSAwLS44LjA3LTEuMDQuMi0uMjMuMTMtLjQ1LjI5LS42NS40OFptMTEuNzUtNC4xNmMwLS4zOC4xLS43Mi4yOC0xLjA0LjE5LS4zMi40NC0uNTcuNzUtLjc2YTEuOTYgMS45NiAwIDAgMSAyLjA1IDBjLjMxLjE5LjU2LjQ0Ljc0Ljc2LjE5LjMyLjI4LjY2LjI4IDEuMDRzLS4xLjczLS4yOCAxLjA1Yy0uMTguMzEtLjQzLjU2LS43NC43NGEyLjA0IDIuMDQgMCAwIDEtMi44LS43NCAyLjAzIDIuMDMgMCAwIDEtLjI4LTEuMDVabTEuMDUgMGExIDEgMCAwIDAgMSAxIC45Ny45NyAwIDAgMCAuOTgtMSAxIDEgMCAwIDAtLjI3LS43Mi45My45MyAwIDAgMC0uNy0uM2MtLjI4IDAtLjUxLjEtLjcxLjMtLjIuMi0uMy40My0uMy43MlptMTIuMTkgNS43NWgxLjk1YTQuNSA0LjUgMCAwIDEtLjYyIDEuOTkgMy43MiAzLjcyIDAgMCAxLTEuNSAxLjM3IDUgNSAwIDAgMS0yLjMzLjUgNC4xNSA0LjE1IDAgMCAxLTMuMzQtMS40NWMtLjQtLjQ4LS43MS0xLjA0LS45Mi0xLjctLjIxLS42Ni0uMzItMS40LS4zMi0yLjIydi0uOTVjMC0uODEuMS0xLjU1LjMyLTIuMjIuMjItLjY2LjUzLTEuMjIuOTQtMS42OS40LS40Ny45LS44NCAxLjQ2LTEuMDlhNC43OCA0Ljc4IDAgMCAxIDEuOTMtLjM3Yy45IDAgMS42Ny4xNyAyLjMuNS42Mi4zMyAxLjEuOCAxLjQ1IDEuMzguMzUuNTkuNTYgMS4yNi42NCAyLjAyaC0xLjk1Yy0uMDUtLjQ4LS4xNy0uOS0uMzUtMS4yNWExLjc3IDEuNzcgMCAwIDAtLjc2LS44IDIuNzMgMi43MyAwIDAgMC0xLjMzLS4yOGMtLjQ1IDAtLjg0LjA4LTEuMTcuMjVhMi4yIDIuMiAwIDAgMC0uODQuNzNjLS4yMi4zMy0uMzkuNzItLjUgMS4yLS4xMS40Ny0uMTcgMS0uMTcgMS42di45N2MwIC41Ny4wNSAxLjEuMTUgMS41Ni4xLjQ3LjI2Ljg2LjQ3IDEuMi4yMS4zMy40OC41OS44MS43Ny4zMy4xOC43Mi4yNyAxLjE4LjI3LjU2IDAgMS0uMDggMS4zNS0uMjYuMzUtLjE4LjYxLS40NC44LS43OC4xNy0uMzQuMy0uNzYuMzUtMS4yNVoiIGZpbGw9IiMwMDAiIGZpbGwtb3BhY2l0eT0iLjg3Ii8+PHJlY3QgeD0iMTYxLjUiIHk9IjE4IiB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHJ4PSIxNiIgZmlsbD0iIzMwNTY4MCIvPjxwYXRoIGQ9Im0xNzUuNSAyOC0xLjQxIDEuNDEgNC41OCA0LjU5LTQuNTggNC41OUwxNzUuNSA0MGw2LTYtNi02WiIgZmlsbD0iI2ZmZiIvPjwvZz48ZGVmcz48ZmlsdGVyIGlkPSJhIiB4PSIuNSIgeT0iLjUiIHdpZHRoPSIyMTMiIGhlaWdodD0iNzUiIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj48ZmVGbG9vZCBmbG9vZC1vcGFjaXR5PSIwIiByZXN1bHQ9IkJhY2tncm91bmRJbWFnZUZpeCIvPjxmZUNvbG9yTWF0cml4IGluPSJTb3VyY2VBbHBoYSIgdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAxMjcgMCIgcmVzdWx0PSJoYXJkQWxwaGEiLz48ZmVPZmZzZXQgZHk9IjQiLz48ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSI0Ii8+PGZlQ29tcG9zaXRlIGluMj0iaGFyZEFscGhhIiBvcGVyYXRvcj0ib3V0Ii8+PGZlQ29sb3JNYXRyaXggdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwLjA0IDAiLz48ZmVCbGVuZCBpbjI9IkJhY2tncm91bmRJbWFnZUZpeCIgcmVzdWx0PSJlZmZlY3QxX2Ryb3BTaGFkb3dfNTY2OV8xNjA3MDUiLz48ZmVCbGVuZCBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJlZmZlY3QxX2Ryb3BTaGFkb3dfNTY2OV8xNjA3MDUiIHJlc3VsdD0ic2hhcGUiLz48L2ZpbHRlcj48L2RlZnM+PC9zdmc+", + "public": true + } + ], + "scada": false, + "tags": [ + "command", + "downlink", + "device configuration", + "device control", + "invocation", + "remote method", + "remote function", + "interface", + "subroutine call", + "inter-process communication", + "server request", + "update attribute", + "set attribute", + "add time-series" + ] +} \ No newline at end of file diff --git a/application/src/main/data/json/tenant/device_profile/rule_chain_template.json b/application/src/main/data/json/tenant/device_profile/rule_chain_template.json index bce88d62b0..0f2473cde6 100644 --- a/application/src/main/data/json/tenant/device_profile/rule_chain_template.json +++ b/application/src/main/data/json/tenant/device_profile/rule_chain_template.json @@ -19,8 +19,13 @@ }, "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode", "name": "Save Timeseries", + "configurationVersion": 1, "configuration": { - "defaultTTL": 0 + "defaultTTL": 0, + "useServerTs": false, + "processingSettings": { + "type": "ON_EVERY_MESSAGE" + } } }, { @@ -134,4 +139,4 @@ ], "ruleChainConnections": null } -} \ No newline at end of file +} diff --git a/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json b/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json index ee38849c1b..8efda98c5b 100644 --- a/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json +++ b/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json @@ -18,8 +18,13 @@ }, "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode", "name": "Save Timeseries", + "configurationVersion": 1, "configuration": { - "defaultTTL": 0 + "defaultTTL": 0, + "useServerTs": false, + "processingSettings": { + "type": "ON_EVERY_MESSAGE" + } } }, { diff --git a/application/src/main/data/upgrade/basic/schema_update.sql b/application/src/main/data/upgrade/basic/schema_update.sql index 64bbbaca05..eccc510f42 100644 --- a/application/src/main/data/upgrade/basic/schema_update.sql +++ b/application/src/main/data/upgrade/basic/schema_update.sql @@ -14,198 +14,50 @@ -- limitations under the License. -- -ALTER TABLE user_credentials ADD COLUMN IF NOT EXISTS last_login_ts BIGINT; -UPDATE user_credentials c SET last_login_ts = (SELECT (additional_info::json ->> 'lastLoginTs')::bigint FROM tb_user u WHERE u.id = c.user_id) - WHERE last_login_ts IS NULL; +-- UPDATE SAVE TIME SERIES NODES START -ALTER TABLE user_credentials ADD COLUMN IF NOT EXISTS failed_login_attempts INT; -UPDATE user_credentials c SET failed_login_attempts = (SELECT (additional_info::json ->> 'failedLoginAttempts')::int FROM tb_user u WHERE u.id = c.user_id) - WHERE failed_login_attempts IS NULL; - -UPDATE tb_user SET additional_info = (additional_info::jsonb - 'lastLoginTs' - 'failedLoginAttempts' - 'userCredentialsEnabled')::text - WHERE additional_info IS NOT NULL AND additional_info != 'null' AND jsonb_typeof(additional_info::jsonb) = 'object'; - --- UPDATE RULE NODE DEBUG MODE TO DEBUG STRATEGY START - -ALTER TABLE rule_node ADD COLUMN IF NOT EXISTS debug_settings varchar(1024) DEFAULT null; -DO -$$ - BEGIN - IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'rule_node' AND column_name = 'debug_mode') - THEN - UPDATE rule_node SET debug_settings = '{"failuresEnabled": true, "allEnabledUntil": ' || cast((extract(epoch from now()) + 900) * 1000 as bigint) || '}' WHERE debug_mode = true; -- 15 minutes according to thingsboard.yml default settings. - ALTER TABLE rule_node DROP COLUMN debug_mode; - END IF; - END -$$; - --- UPDATE RULE NODE DEBUG MODE TO DEBUG STRATEGY END - - --- CREATE MOBILE APP BUNDLES FROM EXISTING APPS - -CREATE TABLE IF NOT EXISTS mobile_app_bundle ( - id uuid NOT NULL CONSTRAINT mobile_app_bundle_pkey PRIMARY KEY, - created_time bigint NOT NULL, - tenant_id uuid, - title varchar(255), - description varchar(1024), - android_app_id uuid UNIQUE, - ios_app_id uuid UNIQUE, - layout_config varchar(16384), - oauth2_enabled boolean, - CONSTRAINT fk_android_app_id FOREIGN KEY (android_app_id) REFERENCES mobile_app(id) ON DELETE SET NULL, - CONSTRAINT fk_ios_app_id FOREIGN KEY (ios_app_id) REFERENCES mobile_app(id) ON DELETE SET NULL -); -CREATE INDEX IF NOT EXISTS mobile_app_bundle_tenant_id ON mobile_app_bundle(tenant_id); - -ALTER TABLE mobile_app ADD COLUMN IF NOT EXISTS platform_type varchar(32), - ADD COLUMN IF NOT EXISTS status varchar(32), - ADD COLUMN IF NOT EXISTS version_info varchar(100000), - ADD COLUMN IF NOT EXISTS store_info varchar(16384), - DROP CONSTRAINT IF EXISTS mobile_app_pkg_name_key, - DROP CONSTRAINT IF EXISTS mobile_app_unq_key; - --- rename mobile_app_oauth2_client to mobile_app_bundle_oauth2_client -DO -$$ - BEGIN - -- in case of running the upgrade script a second time - IF EXISTS(SELECT 1 FROM information_schema.tables WHERE table_name = 'mobile_app_oauth2_client') THEN - ALTER TABLE mobile_app_oauth2_client RENAME TO mobile_app_bundle_oauth2_client; - ALTER TABLE mobile_app_bundle_oauth2_client DROP CONSTRAINT IF EXISTS fk_domain; - ALTER TABLE mobile_app_bundle_oauth2_client RENAME COLUMN mobile_app_id TO mobile_app_bundle_id; - END IF; - END; -$$; - - -CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; - --- duplicate each mobile app and create mobile app bundle for the pair of android and ios app -DO -$$ - DECLARE - generatedBundleId uuid; - iosAppId uuid; - mobileAppRecord RECORD; +DO $$ BEGIN - -- in case of running the upgrade script a second time - IF EXISTS(SELECT 1 FROM information_schema.columns WHERE table_name = 'mobile_app' and column_name = 'oauth2_enabled') THEN - UPDATE mobile_app SET platform_type = 'ANDROID' WHERE platform_type IS NULL; - UPDATE mobile_app SET status = 'DRAFT' WHERE mobile_app.status IS NULL; - FOR mobileAppRecord IN SELECT * FROM mobile_app - LOOP - -- duplicate app for iOS platform type - iosAppId := uuid_generate_v4(); - INSERT INTO mobile_app(id, created_time, tenant_id, pkg_name, app_secret, platform_type, status) - VALUES (iosAppId, mobileAppRecord.created_time, mobileAppRecord.tenant_id, mobileAppRecord.pkg_name, mobileAppRecord.app_secret, 'IOS', mobileAppRecord.status) - ON CONFLICT DO NOTHING; - -- create bundle for android and iOS app - generatedBundleId := uuid_generate_v4(); - INSERT INTO mobile_app_bundle(id, created_time, tenant_id, title, android_app_id, ios_app_id, oauth2_enabled) - VALUES (generatedBundleId, mobileAppRecord.created_time, mobileAppRecord.tenant_id, - mobileAppRecord.pkg_name || ' (autogenerated)', mobileAppRecord.id, iosAppId, mobileAppRecord.oauth2_enabled) - ON CONFLICT DO NOTHING; - UPDATE mobile_app_bundle_oauth2_client SET mobile_app_bundle_id = generatedBundleId WHERE mobile_app_bundle_id = mobileAppRecord.id; - END LOOP; - END IF; - IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'fk_mobile_app_bundle_oauth2_client_bundle_id') THEN - ALTER TABLE mobile_app_bundle_oauth2_client ADD CONSTRAINT fk_mobile_app_bundle_oauth2_client_bundle_id - FOREIGN KEY (mobile_app_bundle_id) REFERENCES mobile_app_bundle(id) ON DELETE CASCADE; - END IF; - ALTER TABLE mobile_app DROP COLUMN IF EXISTS oauth2_enabled; - IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'mobile_app_pkg_name_platform_unq_key') THEN - ALTER TABLE mobile_app ADD CONSTRAINT mobile_app_pkg_name_platform_unq_key UNIQUE (pkg_name, platform_type); - END IF; - END; -$$; + -- Check if the rule_node table exists + IF EXISTS ( + SELECT 1 + FROM information_schema.tables + WHERE table_name = 'rule_node' + ) THEN + + UPDATE rule_node + SET configuration = ( + (configuration::jsonb - 'skipLatestPersistence') + || jsonb_build_object( + 'processingSettings', jsonb_build_object( + 'type', 'ADVANCED', + 'timeseries', jsonb_build_object('type', 'ON_EVERY_MESSAGE'), + 'latest', jsonb_build_object('type', 'SKIP'), + 'webSockets', jsonb_build_object('type', 'ON_EVERY_MESSAGE') + ) + ) + )::text, + configuration_version = 1 + WHERE type = 'org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode' + AND configuration_version = 0 + AND configuration::jsonb ->> 'skipLatestPersistence' = 'true'; + + UPDATE rule_node + SET configuration = ( + (configuration::jsonb - 'skipLatestPersistence') + || jsonb_build_object( + 'processingSettings', jsonb_build_object( + 'type', 'ON_EVERY_MESSAGE' + ) + ) + )::text, + configuration_version = 1 + WHERE type = 'org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode' + AND configuration_version = 0 + AND (configuration::jsonb ->> 'skipLatestPersistence' != 'true' OR configuration::jsonb ->> 'skipLatestPersistence' IS NULL); -ALTER TABLE IF EXISTS mobile_app_settings RENAME TO qr_code_settings; -ALTER TABLE qr_code_settings ADD COLUMN IF NOT EXISTS mobile_app_bundle_id uuid, - ADD COLUMN IF NOT EXISTS android_enabled boolean, - ADD COLUMN IF NOT EXISTS ios_enabled boolean; - --- migrate mobile apps from qr code settings to mobile_app, create mobile app bundle for the pair of apps -DO -$$ - DECLARE - androidPkgName varchar; - iosPkgName varchar; - androidAppId uuid; - iosAppId uuid; - generatedBundleId uuid; - qrCodeRecord RECORD; - BEGIN - -- in case of running the upgrade script a second time - IF EXISTS(SELECT 1 FROM information_schema.columns WHERE table_name = 'qr_code_settings' AND column_name = 'android_config') THEN - FOR qrCodeRecord IN SELECT * FROM qr_code_settings - LOOP - generatedBundleId := NULL; - -- migrate android config - IF (qrCodeRecord.android_config::jsonb ->> 'appPackage' IS NOT NULL) THEN - androidPkgName := qrCodeRecord.android_config::jsonb ->> 'appPackage'; - SELECT id into androidAppId FROM mobile_app WHERE pkg_name = androidPkgName AND platform_type = 'ANDROID'; - IF androidAppId IS NULL THEN - androidAppId := uuid_generate_v4(); - INSERT INTO mobile_app(id, created_time, tenant_id, pkg_name, platform_type, status, store_info) - VALUES (androidAppId, (extract(epoch from now()) * 1000), qrCodeRecord.tenant_id, - androidPkgName, 'ANDROID', 'DRAFT', qrCodeRecord.android_config::jsonb - 'appPackage' - 'enabled'); - generatedBundleId := uuid_generate_v4(); - INSERT INTO mobile_app_bundle(id, created_time, tenant_id, title, android_app_id) - VALUES (generatedBundleId, (extract(epoch from now()) * 1000), qrCodeRecord.tenant_id, androidPkgName || ' (autogenerated)', androidAppId); - UPDATE qr_code_settings SET mobile_app_bundle_id = generatedBundleId; - ELSE - UPDATE mobile_app SET store_info = qrCodeRecord.android_config::jsonb - 'appPackage' - 'enabled' WHERE id = androidAppId; - UPDATE qr_code_settings SET mobile_app_bundle_id = (SELECT id FROM mobile_app_bundle WHERE mobile_app_bundle.android_app_id = androidAppId); - END IF; - END IF; - UPDATE qr_code_settings SET android_enabled = (qrCodeRecord.android_config::jsonb ->> 'enabled')::boolean WHERE id = qrCodeRecord.id; - - -- migrate ios config - IF (qrCodeRecord.ios_config::jsonb ->> 'appId' IS NOT NULL) THEN - iosPkgName := substring(qrCodeRecord.ios_config::jsonb ->> 'appId', strpos(qrCodeRecord.ios_config::jsonb ->> 'appId', '.') + 1); - SELECT id INTO iosAppId FROM mobile_app WHERE pkg_name = iosPkgName AND platform_type = 'IOS'; - IF iosAppId IS NULL THEN - iosAppId := uuid_generate_v4(); - INSERT INTO mobile_app(id, created_time, tenant_id, pkg_name, platform_type, status, store_info) - VALUES (iosAppId, (extract(epoch from now()) * 1000), qrCodeRecord.tenant_id, - iosPkgName, 'IOS', 'DRAFT', qrCodeRecord.ios_config::jsonb - 'enabled'); - IF generatedBundleId IS NULL THEN - generatedBundleId := uuid_generate_v4(); - INSERT INTO mobile_app_bundle(id, created_time, tenant_id, title, ios_app_id) - VALUES (generatedBundleId, (extract(epoch from now()) * 1000), qrCodeRecord.tenant_id, iosPkgName || ' (autogenerated)', iosAppId); - UPDATE qr_code_settings SET mobile_app_bundle_id = generatedBundleId; - ELSE - UPDATE mobile_app_bundle SET ios_app_id = iosAppId WHERE id = generatedBundleId; - END IF; - ELSE - UPDATE qr_code_settings SET mobile_app_bundle_id = (SELECT id FROM mobile_app_bundle WHERE mobile_app_bundle.ios_app_id = iosAppId); - UPDATE mobile_app SET store_info = qrCodeRecord.ios_config::jsonb - 'enabled' WHERE id = iosAppId; - END IF; - END IF; - UPDATE qr_code_settings SET ios_enabled = (qrCodeRecord.ios_config::jsonb -> 'enabled')::boolean WHERE id = qrCodeRecord.id; - END LOOP; - ALTER TABLE qr_code_settings RENAME CONSTRAINT mobile_app_settings_tenant_id_unq_key TO qr_code_settings_tenant_id_unq_key; - ALTER TABLE qr_code_settings RENAME CONSTRAINT mobile_app_settings_pkey TO qr_code_settings_pkey; - END IF; - ALTER TABLE qr_code_settings DROP COLUMN IF EXISTS android_config, DROP COLUMN IF EXISTS ios_config; - END; -$$; - --- update constraint name -DO -$$ - BEGIN - ALTER TABLE domain DROP CONSTRAINT IF EXISTS domain_unq_key; - IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'domain_name_key') THEN - ALTER TABLE domain ADD CONSTRAINT domain_name_key UNIQUE (name); END IF; END; $$; --- UPDATE RESOURCE JS_MODULE SUB TYPE START - -UPDATE resource SET resource_sub_type = 'EXTENSION' WHERE resource_type = 'JS_MODULE' AND resource_sub_type IS NULL; - --- UPDATE RESOURCE JS_MODULE SUB TYPE END +-- UPDATE SAVE TIME SERIES NODES END diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java index 3dca3417b4..a29fcfc362 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java @@ -126,8 +126,8 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso final TenantId tenantId; final DeviceId deviceId; final LinkedHashMapRemoveEldest sessions; - private final Map attributeSubscriptions; - private final Map rpcSubscriptions; + final Map attributeSubscriptions; + final Map rpcSubscriptions; private final Map toDeviceRpcPendingMap; private final boolean rpcSequential; private final RpcSubmitStrategy rpcSubmitStrategy; @@ -865,6 +865,8 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso } private void notifyTransportAboutClosedSessionMaxSessionsLimit(UUID sessionId, SessionInfoMetaData sessionMd) { + attributeSubscriptions.remove(sessionId); + rpcSubscriptions.remove(sessionId); notifyTransportAboutClosedSession(sessionId, sessionMd, TransportSessionCloseReason.MAX_CONCURRENT_SESSIONS_LIMIT_REACHED); } diff --git a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java index c32533bd04..0f3f529cb7 100644 --- a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java @@ -84,6 +84,7 @@ import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; public class SwaggerConfiguration { public static final String LOGIN_ENDPOINT = "/api/auth/login"; + public static final String REFRESH_TOKEN_ENDPOINT = "/api/auth/token"; private static final ApiResponses loginResponses = loginResponses(); private static final ApiResponses defaultErrorResponses = defaultErrorResponses(false); @@ -150,6 +151,7 @@ public class SwaggerConfiguration { .info(info); addDefaultSchemas(openApi); addLoginOperation(openApi); + addRefreshTokenOperation(openApi); return openApi; } @@ -210,6 +212,29 @@ public class SwaggerConfiguration { openAPI.path(LOGIN_ENDPOINT, pathItem); } + private void addRefreshTokenOperation(OpenAPI openAPI) { + var operation = new Operation(); + operation.summary("Refresh user JWT token data"); + operation.description(""" + Method to refresh JWT token. Provide a valid refresh token to get a new JWT token. + + The response contains a new token that can be used for authorization. + + `X-Authorization: Bearer $JWT_TOKEN_VALUE`"""); + + var requestBody = new RequestBody().description("Refresh token request") + .content(new Content().addMediaType(APPLICATION_JSON_VALUE, + new MediaType().schema(new Schema().addProperty("refreshToken", new Schema<>().type("string"))))); + + operation.requestBody(requestBody); + + operation.responses(loginResponses); + + operation.addTagsItem("login-endpoint"); + var pathItem = new PathItem().post(operation); + openAPI.path(REFRESH_TOKEN_ENDPOINT, pathItem); + } + @Bean public GroupedOpenApi groupedApi(SpringDocParameterNameDiscoverer localSpringDocParameterNameDiscoverer) { return GroupedOpenApi.builder() 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 dbe430ba18..2b248e57b3 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -125,6 +125,7 @@ import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.util.ThrowingBiFunction; import org.thingsboard.server.common.data.widget.WidgetTypeDetails; +import org.thingsboard.server.common.data.widget.WidgetTypeInfo; import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.dao.alarm.AlarmCommentService; import org.thingsboard.server.dao.asset.AssetProfileService; @@ -759,6 +760,10 @@ public abstract class BaseController { return checkEntityId(widgetTypeId, widgetTypeService::findWidgetTypeDetailsById, operation); } + WidgetTypeInfo checkWidgetTypeInfoId(WidgetTypeId widgetTypeId, Operation operation) throws ThingsboardException { + return checkEntityId(widgetTypeId, widgetTypeService::findWidgetTypeInfoById, operation); + } + Dashboard checkDashboardId(DashboardId dashboardId, Operation operation) throws ThingsboardException { return checkEntityId(dashboardId, dashboardService::findDashboardById, operation); } diff --git a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java index 0d4b988771..2f7128f97c 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java @@ -217,7 +217,7 @@ public class TbResourceController extends BaseController { @RequestBody TbResource resource) throws Exception { resource.setTenantId(getTenantId()); checkEntity(resource.getId(), resource, Resource.TB_RESOURCE); - return new TbResourceInfo(tbResourceService.save(resource, getCurrentUser())); + return tbResourceService.save(resource, getCurrentUser()); } @ApiOperation(value = "Get Resource Infos (getResources)", diff --git a/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java b/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java index e4973a71c3..2c61ecac79 100644 --- a/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java +++ b/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java @@ -118,7 +118,7 @@ public class WidgetTypeController extends AutoCommitController { @PathVariable("widgetTypeId") String strWidgetTypeId) throws ThingsboardException { checkParameter("widgetTypeId", strWidgetTypeId); WidgetTypeId widgetTypeId = new WidgetTypeId(toUUID(strWidgetTypeId)); - return new WidgetTypeInfo(checkWidgetTypeId(widgetTypeId, Operation.READ)); + return checkWidgetTypeInfoId(widgetTypeId, Operation.READ); } @ApiOperation(value = "Create Or Update Widget Type (saveWidgetType)", diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index 622c0ab2dc..ea4c059222 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -35,8 +35,6 @@ import org.thingsboard.server.service.install.migrate.TsLatestMigrateService; import org.thingsboard.server.service.install.update.CacheCleanupService; import org.thingsboard.server.service.install.update.DataUpdateService; -import static org.thingsboard.server.service.install.update.DefaultDataUpdateService.getEnv; - @Service @Profile("install") @Slf4j @@ -99,8 +97,6 @@ public class ThingsboardInstallService { if ("cassandra-latest-to-postgres".equals(upgradeFromVersion)) { log.info("Migrating ThingsBoard latest timeseries data from cassandra to SQL database ..."); latestMigrateService.migrate(); - } else if (upgradeFromVersion.equals("3.9.0-resources")) { - installScripts.updateResourcesUsage(); } else { // TODO DON'T FORGET to update SUPPORTED_VERSIONS_FROM in DefaultDatabaseSchemaSettingsService databaseSchemaVersionService.validateSchemaSettings(); @@ -118,25 +114,16 @@ public class ThingsboardInstallService { entityDatabaseSchemaService.createOrUpdateDeviceInfoView(persistToTelemetry); // Creates missing indexes. entityDatabaseSchemaService.createDatabaseIndexes(); - // Runs upgrade scripts that are not possible in plain SQL. + // TODO: cleanup update code after each release - if (!getEnv("SKIP_RESOURCES_USAGE_MIGRATION", false)) { - installScripts.setUpdateResourcesUsage(true); - } else { - log.info("Skipping resources usage migration. Run the upgrade with fromVersion as '3.9.0-resources' to migrate"); - } - if (installScripts.isUpdateResourcesUsage()) { - installScripts.updateResourcesUsage(); - } + + // Runs upgrade scripts that are not possible in plain SQL. dataUpdateService.updateData(); log.info("Updating system data..."); dataUpdateService.upgradeRuleNodes(); systemDataLoaderService.loadSystemWidgets(); installScripts.loadSystemLwm2mResources(); installScripts.loadSystemImagesAndResources(); - if (installScripts.isUpdateImages()) { - installScripts.updateImages(); - } databaseSchemaVersionService.updateSchemaVersion(); } log.info("Upgrade finished successfully!"); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java index ee50bbb4a3..2ea64893d0 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java @@ -16,12 +16,13 @@ package org.thingsboard.server.service.edge; import lombok.Data; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import org.thingsboard.server.cache.limits.RateLimitService; import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; import org.thingsboard.server.dao.alarm.AlarmCommentService; import org.thingsboard.server.dao.alarm.AlarmService; @@ -55,50 +56,46 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.rpc.EdgeEventStorageSettings; import org.thingsboard.server.service.edge.rpc.EdgeRpcService; -import org.thingsboard.server.service.edge.rpc.constructor.asset.AssetMsgConstructorFactory; -import org.thingsboard.server.service.edge.rpc.constructor.device.DeviceMsgConstructorFactory; -import org.thingsboard.server.service.edge.rpc.constructor.edge.EdgeMsgConstructor; -import org.thingsboard.server.service.edge.rpc.processor.alarm.AlarmEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.alarm.AlarmEdgeProcessorFactory; +import org.thingsboard.server.service.edge.rpc.processor.EdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.alarm.AlarmProcessor; +import org.thingsboard.server.service.edge.rpc.processor.alarm.comment.AlarmCommentProcessor; import org.thingsboard.server.service.edge.rpc.processor.asset.AssetEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.asset.AssetEdgeProcessorFactory; import org.thingsboard.server.service.edge.rpc.processor.asset.profile.AssetProfileEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.asset.profile.AssetProfileEdgeProcessorFactory; -import org.thingsboard.server.service.edge.rpc.processor.customer.CustomerEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.dashboard.DashboardEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.dashboard.DashboardEdgeProcessorFactory; import org.thingsboard.server.service.edge.rpc.processor.device.DeviceEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.device.DeviceEdgeProcessorFactory; import org.thingsboard.server.service.edge.rpc.processor.device.profile.DeviceProfileEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.device.profile.DeviceProfileEdgeProcessorFactory; -import org.thingsboard.server.service.edge.rpc.processor.edge.EdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.entityview.EntityViewEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.entityview.EntityViewProcessorFactory; -import org.thingsboard.server.service.edge.rpc.processor.notification.NotificationEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.oauth2.OAuth2EdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.ota.OtaPackageEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.queue.QueueEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.relation.RelationEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.relation.RelationEdgeProcessorFactory; import org.thingsboard.server.service.edge.rpc.processor.resource.ResourceEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.resource.ResourceEdgeProcessorFactory; import org.thingsboard.server.service.edge.rpc.processor.rule.RuleChainEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.settings.AdminSettingsEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.rule.RuleChainMetadataEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.telemetry.TelemetryEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.tenant.TenantEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.tenant.TenantProfileEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.user.UserEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.widget.WidgetBundleEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.widget.WidgetTypeEdgeProcessor; import org.thingsboard.server.service.edge.rpc.sync.EdgeRequestsService; import org.thingsboard.server.service.executors.GrpcCallbackExecutorService; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + @Lazy @Data +@Slf4j @Component @TbCoreComponent public class EdgeContextComponent { + private final Map processorMap = new EnumMap<>(EdgeEventType.class); + + @Autowired + public EdgeContextComponent(List processors) { + processors.forEach(processor -> { + EdgeEventType eventType = processor.getEdgeEventType(); + if (eventType != null) { + processorMap.put(eventType, processor); + } + }); + } + // services @Autowired private AdminSettingsService adminSettingsService; @@ -202,10 +199,10 @@ public class EdgeContextComponent { // processors @Autowired - private AdminSettingsEdgeProcessor adminSettingsProcessor; + private AlarmProcessor alarmProcessor; @Autowired - private AlarmEdgeProcessor alarmProcessor; + private AlarmCommentProcessor alarmCommentProcessor; @Autowired private AssetEdgeProcessor assetProcessor; @@ -213,9 +210,6 @@ public class EdgeContextComponent { @Autowired private AssetProfileEdgeProcessor assetProfileProcessor; - @Autowired - private CustomerEdgeProcessor customerProcessor; - @Autowired private DashboardEdgeProcessor dashboardProcessor; @@ -225,26 +219,11 @@ public class EdgeContextComponent { @Autowired private DeviceProfileEdgeProcessor deviceProfileProcessor; - @Autowired - private EdgeProcessor edgeProcessor; - @Autowired private EntityViewEdgeProcessor entityViewProcessor; @Autowired - private NotificationEdgeProcessor notificationEdgeProcessor; - - @Autowired - private NotificationRuleProcessor notificationRuleProcessor; - - @Autowired - private OAuth2EdgeProcessor oAuth2EdgeProcessor; - - @Autowired - private OtaPackageEdgeProcessor otaPackageProcessor; - - @Autowired - private QueueEdgeProcessor queueProcessor; + private NotificationRuleProcessor ruleProcessor; @Autowired private RelationEdgeProcessor relationProcessor; @@ -256,60 +235,10 @@ public class EdgeContextComponent { private RuleChainEdgeProcessor ruleChainProcessor; @Autowired - private TelemetryEdgeProcessor telemetryProcessor; + private RuleChainMetadataEdgeProcessor ruleChainMetadataProcessor; @Autowired - private TenantEdgeProcessor tenantProcessor; - - @Autowired - private TenantProfileEdgeProcessor tenantProfileProcessor; - - @Autowired - private UserEdgeProcessor userProcessor; - - @Autowired - private WidgetBundleEdgeProcessor widgetBundleProcessor; - - @Autowired - private WidgetTypeEdgeProcessor widgetTypeProcessor; - - // msg constructors - @Autowired - private EdgeMsgConstructor edgeMsgConstructor; - - // factories - @Autowired - private AlarmEdgeProcessorFactory alarmEdgeProcessorFactory; - - @Autowired - private AssetEdgeProcessorFactory assetEdgeProcessorFactory; - - @Autowired - private AssetMsgConstructorFactory assetMsgConstructorFactory; - - @Autowired - private AssetProfileEdgeProcessorFactory assetProfileEdgeProcessorFactory; - - @Autowired - private DashboardEdgeProcessorFactory dashboardEdgeProcessorFactory; - - @Autowired - private DeviceEdgeProcessorFactory deviceEdgeProcessorFactory; - - @Autowired - private DeviceMsgConstructorFactory deviceMsgConstructorFactory; - - @Autowired - private DeviceProfileEdgeProcessorFactory deviceProfileEdgeProcessorFactory; - - @Autowired - private EntityViewProcessorFactory entityViewProcessorFactory; - - @Autowired - private RelationEdgeProcessorFactory relationEdgeProcessorFactory; - - @Autowired - private ResourceEdgeProcessorFactory resourceEdgeProcessorFactory; + private TelemetryEdgeProcessor telemetryProcessor; // config @Autowired @@ -319,4 +248,12 @@ public class EdgeContextComponent { @Autowired private GrpcCallbackExecutorService grpcCallbackExecutorService; + public EdgeProcessor getProcessor(EdgeEventType edgeEventType) { + EdgeProcessor processor = processorMap.get(edgeEventType); + if (processor == null) { + throw new UnsupportedOperationException("No processor found for EdgeEventType: " + edgeEventType); + } + return processor; + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtils.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtils.java new file mode 100644 index 0000000000..d04cebc70a --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtils.java @@ -0,0 +1,552 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge; + +import com.fasterxml.jackson.databind.JsonNode; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.reflect.TypeToken; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.adaptor.JsonConverter; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Dashboard; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.OtaPackage; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.TbResource; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmComment; +import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.asset.AssetProfile; +import org.thingsboard.server.common.data.domain.DomainInfo; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.id.AssetId; +import org.thingsboard.server.common.data.id.AssetProfileId; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DashboardId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.DomainId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.EntityViewId; +import org.thingsboard.server.common.data.id.NotificationRuleId; +import org.thingsboard.server.common.data.id.NotificationTargetId; +import org.thingsboard.server.common.data.id.NotificationTemplateId; +import org.thingsboard.server.common.data.id.OAuth2ClientId; +import org.thingsboard.server.common.data.id.OtaPackageId; +import org.thingsboard.server.common.data.id.QueueId; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.TbResourceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.id.WidgetTypeId; +import org.thingsboard.server.common.data.id.WidgetsBundleId; +import org.thingsboard.server.common.data.notification.rule.NotificationRule; +import org.thingsboard.server.common.data.notification.targets.NotificationTarget; +import org.thingsboard.server.common.data.notification.template.NotificationTemplate; +import org.thingsboard.server.common.data.oauth2.OAuth2Client; +import org.thingsboard.server.common.data.queue.Queue; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.UserCredentials; +import org.thingsboard.server.common.data.widget.WidgetTypeDetails; +import org.thingsboard.server.common.data.widget.WidgetsBundle; +import org.thingsboard.server.gen.edge.v1.AlarmCommentUpdateMsg; +import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; +import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg; +import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg; +import org.thingsboard.server.gen.edge.v1.AttributeDeleteMsg; +import org.thingsboard.server.gen.edge.v1.CustomerUpdateMsg; +import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg; +import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg; +import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg; +import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg; +import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; +import org.thingsboard.server.gen.edge.v1.EdgeConfiguration; +import org.thingsboard.server.gen.edge.v1.EntityDataProto; +import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg; +import org.thingsboard.server.gen.edge.v1.NotificationRuleUpdateMsg; +import org.thingsboard.server.gen.edge.v1.NotificationTargetUpdateMsg; +import org.thingsboard.server.gen.edge.v1.NotificationTemplateUpdateMsg; +import org.thingsboard.server.gen.edge.v1.OAuth2ClientUpdateMsg; +import org.thingsboard.server.gen.edge.v1.OAuth2DomainUpdateMsg; +import org.thingsboard.server.gen.edge.v1.OtaPackageUpdateMsg; +import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg; +import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg; +import org.thingsboard.server.gen.edge.v1.ResourceUpdateMsg; +import org.thingsboard.server.gen.edge.v1.RpcRequestMsg; +import org.thingsboard.server.gen.edge.v1.RpcResponseMsg; +import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; +import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg; +import org.thingsboard.server.gen.edge.v1.TenantProfileUpdateMsg; +import org.thingsboard.server.gen.edge.v1.TenantUpdateMsg; +import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.gen.edge.v1.UserCredentialsUpdateMsg; +import org.thingsboard.server.gen.edge.v1.UserUpdateMsg; +import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg; +import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg; +import org.thingsboard.server.gen.transport.TransportProtos; + +import java.util.List; +import java.util.UUID; + +@Slf4j +public class EdgeMsgConstructorUtils { + + public static AlarmUpdateMsg constructAlarmUpdatedMsg(UpdateMsgType msgType, Alarm alarm) { + return AlarmUpdateMsg.newBuilder().setMsgType(msgType) + .setEntity(JacksonUtil.toString(alarm)) + .setIdMSB(alarm.getId().getId().getMostSignificantBits()) + .setIdLSB(alarm.getId().getId().getLeastSignificantBits()).build(); + } + + public static AlarmCommentUpdateMsg constructAlarmCommentUpdatedMsg(UpdateMsgType msgType, AlarmComment alarmComment) { + return AlarmCommentUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(alarmComment)).build(); + } + + public static AssetUpdateMsg constructAssetUpdatedMsg(UpdateMsgType msgType, Asset asset) { + return AssetUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(asset)) + .setIdMSB(asset.getUuidId().getMostSignificantBits()) + .setIdLSB(asset.getUuidId().getLeastSignificantBits()).build(); + } + + public static AssetUpdateMsg constructAssetDeleteMsg(AssetId assetId) { + return AssetUpdateMsg.newBuilder() + .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) + .setIdMSB(assetId.getId().getMostSignificantBits()) + .setIdLSB(assetId.getId().getLeastSignificantBits()).build(); + } + + public static AssetProfileUpdateMsg constructAssetProfileUpdatedMsg(UpdateMsgType msgType, AssetProfile assetProfile) { + return AssetProfileUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(assetProfile)) + .setIdMSB(assetProfile.getId().getId().getMostSignificantBits()) + .setIdLSB(assetProfile.getId().getId().getLeastSignificantBits()).build(); + } + + public static AssetProfileUpdateMsg constructAssetProfileDeleteMsg(AssetProfileId assetProfileId) { + return AssetProfileUpdateMsg.newBuilder() + .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) + .setIdMSB(assetProfileId.getId().getMostSignificantBits()) + .setIdLSB(assetProfileId.getId().getLeastSignificantBits()).build(); + } + + public static CustomerUpdateMsg constructCustomerUpdatedMsg(UpdateMsgType msgType, Customer customer) { + return CustomerUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(customer)) + .setIdMSB(customer.getId().getId().getMostSignificantBits()) + .setIdLSB(customer.getId().getId().getLeastSignificantBits()).build(); + } + + public static CustomerUpdateMsg constructCustomerDeleteMsg(CustomerId customerId) { + return CustomerUpdateMsg.newBuilder() + .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) + .setIdMSB(customerId.getId().getMostSignificantBits()) + .setIdLSB(customerId.getId().getLeastSignificantBits()).build(); + } + + public static DashboardUpdateMsg constructDashboardUpdatedMsg(UpdateMsgType msgType, Dashboard dashboard) { + return DashboardUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(dashboard)) + .setIdMSB(dashboard.getId().getId().getMostSignificantBits()) + .setIdLSB(dashboard.getId().getId().getLeastSignificantBits()).build(); + } + + public static DashboardUpdateMsg constructDashboardDeleteMsg(DashboardId dashboardId) { + return DashboardUpdateMsg.newBuilder() + .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) + .setIdMSB(dashboardId.getId().getMostSignificantBits()) + .setIdLSB(dashboardId.getId().getLeastSignificantBits()).build(); + } + + public static DeviceUpdateMsg constructDeviceUpdatedMsg(UpdateMsgType msgType, Device device) { + return DeviceUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(device)) + .setIdMSB(device.getId().getId().getMostSignificantBits()) + .setIdLSB(device.getId().getId().getLeastSignificantBits()).build(); + } + + public static DeviceUpdateMsg constructDeviceDeleteMsg(DeviceId deviceId) { + return DeviceUpdateMsg.newBuilder() + .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) + .setIdMSB(deviceId.getId().getMostSignificantBits()) + .setIdLSB(deviceId.getId().getLeastSignificantBits()).build(); + } + + public static DeviceCredentialsUpdateMsg constructDeviceCredentialsUpdatedMsg(DeviceCredentials deviceCredentials) { + return DeviceCredentialsUpdateMsg.newBuilder().setEntity(JacksonUtil.toString(deviceCredentials)).build(); + } + + public static DeviceProfileUpdateMsg constructDeviceProfileUpdatedMsg(UpdateMsgType msgType, DeviceProfile deviceProfile) { + return DeviceProfileUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(deviceProfile)) + .setIdMSB(deviceProfile.getId().getId().getMostSignificantBits()) + .setIdLSB(deviceProfile.getId().getId().getLeastSignificantBits()).build(); + } + + public static DeviceProfileUpdateMsg constructDeviceProfileDeleteMsg(DeviceProfileId deviceProfileId) { + return DeviceProfileUpdateMsg.newBuilder() + .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) + .setIdMSB(deviceProfileId.getId().getMostSignificantBits()) + .setIdLSB(deviceProfileId.getId().getLeastSignificantBits()).build(); + } + + public static DeviceRpcCallMsg constructDeviceRpcCallMsg(UUID deviceId, JsonNode body) { + DeviceRpcCallMsg.Builder builder = constructDeviceRpcMsg(deviceId, body); + if (body.has("error") || body.has("response")) { + RpcResponseMsg.Builder responseBuilder = RpcResponseMsg.newBuilder(); + if (body.has("error")) { + responseBuilder.setError(body.get("error").asText()); + } else { + responseBuilder.setResponse(body.get("response").asText()); + } + builder.setResponseMsg(responseBuilder.build()); + } else { + RpcRequestMsg.Builder requestBuilder = RpcRequestMsg.newBuilder(); + requestBuilder.setMethod(body.get("method").asText()); + requestBuilder.setParams(body.get("params").asText()); + builder.setRequestMsg(requestBuilder.build()); + } + return builder.build(); + } + + private static DeviceRpcCallMsg.Builder constructDeviceRpcMsg(UUID deviceId, JsonNode body) { + DeviceRpcCallMsg.Builder builder = DeviceRpcCallMsg.newBuilder() + .setDeviceIdMSB(deviceId.getMostSignificantBits()) + .setDeviceIdLSB(deviceId.getLeastSignificantBits()) + .setRequestId(body.get("requestId").asInt()); + if (body.get("oneway") != null) { + builder.setOneway(body.get("oneway").asBoolean()); + } + if (body.get("requestUUID") != null) { + UUID requestUUID = UUID.fromString(body.get("requestUUID").asText()); + builder.setRequestUuidMSB(requestUUID.getMostSignificantBits()) + .setRequestUuidLSB(requestUUID.getLeastSignificantBits()); + } + if (body.get("expirationTime") != null) { + builder.setExpirationTime(body.get("expirationTime").asLong()); + } + if (body.get("persisted") != null) { + builder.setPersisted(body.get("persisted").asBoolean()); + } + if (body.get("retries") != null) { + builder.setRetries(body.get("retries").asInt()); + } + if (body.get("additionalInfo") != null) { + builder.setAdditionalInfo(JacksonUtil.toString(body.get("additionalInfo"))); + } + if (body.get("serviceId") != null) { + builder.setServiceId(body.get("serviceId").asText()); + } + if (body.get("sessionId") != null) { + builder.setSessionId(body.get("sessionId").asText()); + } + return builder; + } + + public static EdgeConfiguration constructEdgeConfiguration(Edge edge) { + EdgeConfiguration.Builder builder = EdgeConfiguration.newBuilder() + .setEdgeIdMSB(edge.getId().getId().getMostSignificantBits()) + .setEdgeIdLSB(edge.getId().getId().getLeastSignificantBits()) + .setTenantIdMSB(edge.getTenantId().getId().getMostSignificantBits()) + .setTenantIdLSB(edge.getTenantId().getId().getLeastSignificantBits()) + .setName(edge.getName()) + .setType(edge.getType()) + .setRoutingKey(edge.getRoutingKey()) + .setSecret(edge.getSecret()) + .setAdditionalInfo(JacksonUtil.toString(edge.getAdditionalInfo())) + .setCloudType("CE"); + if (edge.getCustomerId() != null) { + builder.setCustomerIdMSB(edge.getCustomerId().getId().getMostSignificantBits()) + .setCustomerIdLSB(edge.getCustomerId().getId().getLeastSignificantBits()); + } + return builder.build(); + } + + public static EntityViewUpdateMsg constructEntityViewUpdatedMsg(UpdateMsgType msgType, EntityView entityView) { + return EntityViewUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(entityView)) + .setIdMSB(entityView.getId().getId().getMostSignificantBits()) + .setIdLSB(entityView.getId().getId().getLeastSignificantBits()).build(); + } + + public static EntityViewUpdateMsg constructEntityViewDeleteMsg(EntityViewId entityViewId) { + return EntityViewUpdateMsg.newBuilder() + .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) + .setIdMSB(entityViewId.getId().getMostSignificantBits()) + .setIdLSB(entityViewId.getId().getLeastSignificantBits()).build(); + } + + public static NotificationRuleUpdateMsg constructNotificationRuleUpdateMsg(UpdateMsgType msgType, NotificationRule notificationRule) { + return NotificationRuleUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(notificationRule)).build(); + } + + public static NotificationRuleUpdateMsg constructNotificationRuleDeleteMsg(NotificationRuleId notificationRuleId) { + return NotificationRuleUpdateMsg.newBuilder() + .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) + .setIdMSB(notificationRuleId.getId().getMostSignificantBits()) + .setIdLSB(notificationRuleId.getId().getLeastSignificantBits()).build(); + } + + public static NotificationTargetUpdateMsg constructNotificationTargetUpdateMsg(UpdateMsgType msgType, NotificationTarget notificationTarget) { + return NotificationTargetUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(notificationTarget)).build(); + } + + public static NotificationTargetUpdateMsg constructNotificationTargetDeleteMsg(NotificationTargetId notificationTargetId) { + return NotificationTargetUpdateMsg.newBuilder() + .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) + .setIdMSB(notificationTargetId.getId().getMostSignificantBits()) + .setIdLSB(notificationTargetId.getId().getLeastSignificantBits()).build(); + } + + public static NotificationTemplateUpdateMsg constructNotificationTemplateUpdateMsg(UpdateMsgType msgType, NotificationTemplate notificationTemplate) { + return NotificationTemplateUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(notificationTemplate)).build(); + } + + public static NotificationTemplateUpdateMsg constructNotificationTemplateDeleteMsg(NotificationTemplateId notificationTemplateId) { + return NotificationTemplateUpdateMsg.newBuilder() + .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) + .setIdMSB(notificationTemplateId.getId().getMostSignificantBits()) + .setIdLSB(notificationTemplateId.getId().getLeastSignificantBits()).build(); + } + + public static OAuth2ClientUpdateMsg constructOAuth2ClientUpdateMsg(UpdateMsgType msgType, OAuth2Client oAuth2Client) { + return OAuth2ClientUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(oAuth2Client)) + .setIdMSB(oAuth2Client.getId().getId().getMostSignificantBits()) + .setIdLSB(oAuth2Client.getId().getId().getLeastSignificantBits()).build(); + } + + public static OAuth2ClientUpdateMsg constructOAuth2ClientDeleteMsg(OAuth2ClientId oAuth2ClientId) { + return OAuth2ClientUpdateMsg.newBuilder() + .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) + .setIdMSB(oAuth2ClientId.getId().getMostSignificantBits()) + .setIdLSB(oAuth2ClientId.getId().getLeastSignificantBits()).build(); + } + + public static OAuth2DomainUpdateMsg constructOAuth2DomainUpdateMsg(UpdateMsgType msgType, DomainInfo domainInfo) { + return OAuth2DomainUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(domainInfo)) + .setIdMSB(domainInfo.getId().getId().getMostSignificantBits()) + .setIdLSB(domainInfo.getId().getId().getLeastSignificantBits()).build(); + } + + public static OAuth2DomainUpdateMsg constructOAuth2DomainDeleteMsg(DomainId domainId) { + return OAuth2DomainUpdateMsg.newBuilder() + .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) + .setIdMSB(domainId.getId().getMostSignificantBits()) + .setIdLSB(domainId.getId().getLeastSignificantBits()) + .build(); + } + + public static OtaPackageUpdateMsg constructOtaPackageUpdatedMsg(UpdateMsgType msgType, OtaPackage otaPackage) { + return OtaPackageUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(otaPackage)) + .setIdMSB(otaPackage.getId().getId().getMostSignificantBits()) + .setIdLSB(otaPackage.getId().getId().getLeastSignificantBits()).build(); + } + + public static OtaPackageUpdateMsg constructOtaPackageDeleteMsg(OtaPackageId otaPackageId) { + return OtaPackageUpdateMsg.newBuilder() + .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) + .setIdMSB(otaPackageId.getId().getMostSignificantBits()) + .setIdLSB(otaPackageId.getId().getLeastSignificantBits()).build(); + } + + public static QueueUpdateMsg constructQueueUpdatedMsg(UpdateMsgType msgType, Queue queue) { + return QueueUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(queue)) + .setIdMSB(queue.getId().getId().getMostSignificantBits()) + .setIdLSB(queue.getId().getId().getLeastSignificantBits()).build(); + } + + public static QueueUpdateMsg constructQueueDeleteMsg(QueueId queueId) { + return QueueUpdateMsg.newBuilder() + .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) + .setIdMSB(queueId.getId().getMostSignificantBits()) + .setIdLSB(queueId.getId().getLeastSignificantBits()).build(); + } + + public static RelationUpdateMsg constructRelationUpdatedMsg(UpdateMsgType msgType, EntityRelation entityRelation) { + return RelationUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(entityRelation)).build(); + } + + public static ResourceUpdateMsg constructResourceUpdatedMsg(UpdateMsgType msgType, TbResource tbResource) { + return ResourceUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(tbResource)) + .setIdMSB(tbResource.getId().getId().getMostSignificantBits()) + .setIdLSB(tbResource.getId().getId().getLeastSignificantBits()).build(); + } + + public static ResourceUpdateMsg constructResourceDeleteMsg(TbResourceId tbResourceId) { + return ResourceUpdateMsg.newBuilder() + .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) + .setIdMSB(tbResourceId.getId().getMostSignificantBits()) + .setIdLSB(tbResourceId.getId().getLeastSignificantBits()).build(); + } + + public static RuleChainUpdateMsg constructRuleChainUpdatedMsg(UpdateMsgType msgType, RuleChain ruleChain, boolean isRoot) { + boolean isTemplateRoot = ruleChain.isRoot(); + ruleChain.setRoot(isRoot); + RuleChainUpdateMsg result = RuleChainUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(ruleChain)) + .setIdMSB(ruleChain.getId().getId().getMostSignificantBits()) + .setIdLSB(ruleChain.getId().getId().getLeastSignificantBits()).build(); + ruleChain.setRoot(isTemplateRoot); + return result; + } + + public static RuleChainUpdateMsg constructRuleChainDeleteMsg(RuleChainId ruleChainId) { + return RuleChainUpdateMsg.newBuilder() + .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) + .setIdMSB(ruleChainId.getId().getMostSignificantBits()) + .setIdLSB(ruleChainId.getId().getLeastSignificantBits()).build(); + } + + public static RuleChainMetadataUpdateMsg constructRuleChainMetadataUpdatedMsg(UpdateMsgType msgType, RuleChainMetaData ruleChainMetaData) { + return RuleChainMetadataUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(ruleChainMetaData)).build(); + } + + public static EntityDataProto constructEntityDataMsg(TenantId tenantId, EntityId entityId, EdgeEventActionType actionType, JsonElement entityData) { + EntityDataProto.Builder builder = EntityDataProto.newBuilder() + .setEntityIdMSB(entityId.getId().getMostSignificantBits()) + .setEntityIdLSB(entityId.getId().getLeastSignificantBits()) + .setEntityType(entityId.getEntityType().name()); + long ts = getTs(entityData.getAsJsonObject()); + switch (actionType) { + case TIMESERIES_UPDATED: + try { + JsonObject data = entityData.getAsJsonObject(); + builder.setPostTelemetryMsg(JsonConverter.convertToTelemetryProto(data.getAsJsonObject("data"), ts)); + } catch (Exception e) { + log.warn("[{}][{}] Can't convert to telemetry proto, entityData [{}]", tenantId, entityId, entityData, e); + } + break; + case ATTRIBUTES_UPDATED: + try { + JsonObject data = entityData.getAsJsonObject(); + TransportProtos.PostAttributeMsg attributesUpdatedMsg = JsonConverter.convertToAttributesProto(data.getAsJsonObject("kv")); + if (data.has("isPostAttributes") && data.getAsJsonPrimitive("isPostAttributes").getAsBoolean()) { + builder.setPostAttributesMsg(attributesUpdatedMsg); + } else { + builder.setAttributesUpdatedMsg(attributesUpdatedMsg); + } + builder.setPostAttributeScope(getScopeOfDefault(data)); + builder.setAttributeTs(ts); + } catch (Exception e) { + log.warn("[{}][{}] Can't convert to AttributesUpdatedMsg proto, entityData [{}]", tenantId, entityId, entityData, e); + } + break; + case POST_ATTRIBUTES: + try { + JsonObject data = entityData.getAsJsonObject(); + TransportProtos.PostAttributeMsg postAttributesMsg = JsonConverter.convertToAttributesProto(data.getAsJsonObject("kv")); + builder.setPostAttributesMsg(postAttributesMsg); + builder.setPostAttributeScope(getScopeOfDefault(data)); + builder.setAttributeTs(ts); + } catch (Exception e) { + log.warn("[{}][{}] Can't convert to PostAttributesMsg, entityData [{}]", tenantId, entityId, entityData, e); + } + break; + case ATTRIBUTES_DELETED: + try { + AttributeDeleteMsg.Builder attributeDeleteMsg = AttributeDeleteMsg.newBuilder(); + attributeDeleteMsg.setScope(entityData.getAsJsonObject().getAsJsonPrimitive("scope").getAsString()); + JsonArray jsonArray = entityData.getAsJsonObject().getAsJsonArray("keys"); + List keys = new Gson().fromJson(jsonArray.toString(), new TypeToken<>(){}.getType()); + attributeDeleteMsg.addAllAttributeNames(keys); + attributeDeleteMsg.build(); + builder.setAttributeDeleteMsg(attributeDeleteMsg); + } catch (Exception e) { + log.warn("[{}][{}] Can't convert to AttributeDeleteMsg proto, entityData [{}]", tenantId, entityId, entityData, e); + } + break; + } + return builder.build(); + } + + private static long getTs(JsonObject data) { + if (data.get("ts") != null && !data.get("ts").isJsonNull()) { + return data.getAsJsonPrimitive("ts").getAsLong(); + } + return System.currentTimeMillis(); + } + + private static String getScopeOfDefault(JsonObject data) { + JsonPrimitive scope = data.getAsJsonPrimitive("scope"); + String result = DataConstants.SERVER_SCOPE; + if (scope != null && StringUtils.isNotBlank(scope.getAsString())) { + result = scope.getAsString(); + } + return result; + } + + public static TenantUpdateMsg constructTenantUpdateMsg(UpdateMsgType msgType, Tenant tenant) { + return TenantUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(tenant)).build(); + } + + public static TenantProfileUpdateMsg constructTenantProfileUpdateMsg(UpdateMsgType msgType, TenantProfile tenantProfile) { + return TenantProfileUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(tenantProfile)).build(); + } + + public static UserUpdateMsg constructUserUpdatedMsg(UpdateMsgType msgType, User user) { + return UserUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(user)) + .setIdMSB(user.getId().getId().getMostSignificantBits()) + .setIdLSB(user.getId().getId().getLeastSignificantBits()).build(); + } + + public static UserUpdateMsg constructUserDeleteMsg(UserId userId) { + return UserUpdateMsg.newBuilder() + .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) + .setIdMSB(userId.getId().getMostSignificantBits()) + .setIdLSB(userId.getId().getLeastSignificantBits()).build(); + } + + public static UserCredentialsUpdateMsg constructUserCredentialsUpdatedMsg(UserCredentials userCredentials) { + return UserCredentialsUpdateMsg.newBuilder().setEntity(JacksonUtil.toString(userCredentials)).build(); + } + + public static WidgetsBundleUpdateMsg constructWidgetsBundleUpdateMsg(UpdateMsgType msgType, WidgetsBundle widgetsBundle, List widgets) { + return WidgetsBundleUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(widgetsBundle)) + .setWidgets(JacksonUtil.toString(widgets)) + .setIdMSB(widgetsBundle.getId().getId().getMostSignificantBits()) + .setIdLSB(widgetsBundle.getId().getId().getLeastSignificantBits()).build(); + } + + public static WidgetsBundleUpdateMsg constructWidgetsBundleDeleteMsg(WidgetsBundleId widgetsBundleId) { + return WidgetsBundleUpdateMsg.newBuilder() + .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) + .setIdMSB(widgetsBundleId.getId().getMostSignificantBits()) + .setIdLSB(widgetsBundleId.getId().getLeastSignificantBits()) + .build(); + } + + public static WidgetTypeUpdateMsg constructWidgetTypeUpdateMsg(UpdateMsgType msgType, WidgetTypeDetails widgetTypeDetails) { + return WidgetTypeUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(widgetTypeDetails)) + .setIdMSB(widgetTypeDetails.getId().getId().getMostSignificantBits()) + .setIdLSB(widgetTypeDetails.getId().getId().getLeastSignificantBits()).build(); + } + + public static WidgetTypeUpdateMsg constructWidgetTypeDeleteMsg(WidgetTypeId widgetTypeId) { + return WidgetTypeUpdateMsg.newBuilder() + .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) + .setIdMSB(widgetTypeId.getId().getMostSignificantBits()) + .setIdLSB(widgetTypeId.getId().getLeastSignificantBits()) + .build(); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java index 7c958343ca..0c8380778e 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java @@ -404,6 +404,9 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i private void scheduleEdgeEventsCheck(EdgeGrpcSession session) { EdgeId edgeId = session.getEdge().getId(); TenantId tenantId = session.getEdge().getTenantId(); + + cancelScheduleEdgeEventsCheck(edgeId); + if (sessions.containsKey(edgeId)) { ScheduledFuture edgeEventCheckTask = edgeEventProcessingExecutorService.schedule(() -> { try { @@ -581,7 +584,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i edgeState.put(ACTIVITY_STATE, false); edgeState.put(LAST_DISCONNECT_TIME, ts); } - ctx.getNotificationRuleProcessor().process(EdgeConnectionTrigger.builder() + ctx.getRuleProcessor().process(EdgeConnectionTrigger.builder() .tenantId(tenantId) .customerId(edge.getCustomerId()) .edgeId(edgeId) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java index 137c4d5bf5..21a1508f7a 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java @@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; @@ -79,17 +80,10 @@ import org.thingsboard.server.gen.edge.v1.UplinkResponseMsg; import org.thingsboard.server.gen.edge.v1.UserCredentialsRequestMsg; import org.thingsboard.server.gen.edge.v1.WidgetBundleTypesRequestMsg; import org.thingsboard.server.service.edge.EdgeContextComponent; +import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; import org.thingsboard.server.service.edge.rpc.fetch.EdgeEventFetcher; import org.thingsboard.server.service.edge.rpc.fetch.GeneralEdgeEventFetcher; -import org.thingsboard.server.service.edge.rpc.processor.alarm.AlarmProcessor; -import org.thingsboard.server.service.edge.rpc.processor.asset.AssetProcessor; -import org.thingsboard.server.service.edge.rpc.processor.asset.profile.AssetProfileProcessor; -import org.thingsboard.server.service.edge.rpc.processor.dashboard.DashboardProcessor; -import org.thingsboard.server.service.edge.rpc.processor.device.DeviceProcessor; -import org.thingsboard.server.service.edge.rpc.processor.device.profile.DeviceProfileProcessor; -import org.thingsboard.server.service.edge.rpc.processor.entityview.EntityViewProcessor; -import org.thingsboard.server.service.edge.rpc.processor.relation.RelationProcessor; -import org.thingsboard.server.service.edge.rpc.processor.resource.ResourceProcessor; +import org.thingsboard.server.service.edge.rpc.utils.EdgeVersionUtils; import java.io.Closeable; import java.util.ArrayList; @@ -238,7 +232,7 @@ public abstract class EdgeGrpcSession implements Closeable { this.tenantId = edge.getTenantId(); this.edge = edge; EdgeUpdateMsg edgeConfig = EdgeUpdateMsg.newBuilder() - .setConfiguration(ctx.getEdgeMsgConstructor().constructEdgeConfiguration(edge)).build(); + .setConfiguration(EdgeMsgConstructorUtils.constructEdgeConfiguration(edge)).build(); ResponseMsg edgeConfigMsg = ResponseMsg.newBuilder() .setEdgeUpdateMsg(edgeConfig) .build(); @@ -355,13 +349,13 @@ public abstract class EdgeGrpcSession implements Closeable { return ConnectResponseMsg.newBuilder() .setResponseCode(ConnectResponseCode.ACCEPTED) .setErrorMsg("") - .setConfiguration(ctx.getEdgeMsgConstructor().constructEdgeConfiguration(edge)) + .setConfiguration(EdgeMsgConstructorUtils.constructEdgeConfiguration(edge)) .setMaxInboundMessageSize(maxInboundMessageSize) .build(); } String error = "Failed to validate the edge!"; String failureMsg = String.format("%s Provided request secret: %s", error, request.getEdgeSecret()); - ctx.getNotificationRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(tenantId).edgeId(edge.getId()) + ctx.getRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(tenantId).edgeId(edge.getId()) .customerId(edge.getCustomerId()).edgeName(edge.getName()).failureMsg(failureMsg).error(error).build()); return ConnectResponseMsg.newBuilder() .setResponseCode(ConnectResponseCode.BAD_CREDENTIALS) @@ -369,7 +363,7 @@ public abstract class EdgeGrpcSession implements Closeable { .setConfiguration(EdgeConfiguration.getDefaultInstance()).build(); } catch (Exception e) { String failureMsg = "Failed to process edge connection!"; - ctx.getNotificationRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(tenantId).edgeId(edge.getId()) + ctx.getRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(tenantId).edgeId(edge.getId()) .customerId(edge.getCustomerId()).edgeName(edge.getName()).failureMsg(failureMsg).error(e.getMessage()).build()); log.error(failureMsg, e); return ConnectResponseMsg.newBuilder() @@ -448,7 +442,11 @@ public abstract class EdgeGrpcSession implements Closeable { private void scheduleDownlinkMsgsPackSend(int attempt) { Runnable sendDownlinkMsgsTask = () -> { try { - if (isConnected() && !sessionState.getPendingMsgsMap().values().isEmpty()) { + if (!isConnected()) { + stopCurrentSendDownlinkMsgsTask(true); + return; + } + if (!sessionState.getPendingMsgsMap().values().isEmpty()) { List copy = new ArrayList<>(sessionState.getPendingMsgsMap().values()); if (attempt > 1) { String error = "Failed to deliver the batch"; @@ -456,7 +454,7 @@ public abstract class EdgeGrpcSession implements Closeable { if (attempt == 2) { // Send a failure notification only on the second attempt. // This ensures that failure alerts are sent just once to avoid redundant notifications. - ctx.getNotificationRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(tenantId) + ctx.getRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(tenantId) .edgeId(edge.getId()).customerId(edge.getCustomerId()).edgeName(edge.getName()).failureMsg(failureMsg).error(error).build()); } log.warn("[{}][{}] {}, attempt: {}", tenantId, sessionId, failureMsg, attempt); @@ -469,7 +467,7 @@ public abstract class EdgeGrpcSession implements Closeable { String message = String.format("Downlink msg size %s exceeds client max inbound message size %s. " + "Please increase value of CLOUD_RPC_MAX_INBOUND_MESSAGE_SIZE env variable on the edge and restart it.", downlinkMsg.getSerializedSize(), clientMaxInboundMessageSize); log.error("[{}][{}][{}] {} Message {}", tenantId, edge.getId(), sessionId, message, downlinkMsg); - ctx.getNotificationRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(tenantId) + ctx.getRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(tenantId) .edgeId(edge.getId()).customerId(edge.getCustomerId()).edgeName(edge.getName()).failureMsg(message).error(error).build()); sessionState.getPendingMsgsMap().remove(downlinkMsg.getDownlinkMsgId()); } else { @@ -484,7 +482,7 @@ public abstract class EdgeGrpcSession implements Closeable { String failureMsg = String.format("Failed to deliver messages: %s", copy); log.warn("[{}][{}] Failed to deliver the batch after {} attempts. Next messages are going to be discarded {}", tenantId, sessionId, MAX_DOWNLINK_ATTEMPTS, copy); - ctx.getNotificationRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(tenantId).edgeId(edge.getId()) + ctx.getRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(tenantId).edgeId(edge.getId()) .customerId(edge.getCustomerId()).edgeName(edge.getName()).failureMsg(failureMsg) .error("Failed to deliver messages after " + MAX_DOWNLINK_ATTEMPTS + " attempts").build()); stopCurrentSendDownlinkMsgsTask(false); @@ -529,6 +527,11 @@ public abstract class EdgeGrpcSession implements Closeable { log.debug("[{}][{}][{}] Msg has been processed successfully! Msg Id: [{}], Msg: {}", tenantId, edge.getId(), sessionId, msg.getDownlinkMsgId(), msg); } else { log.error("[{}][{}][{}] Msg processing failed! Msg Id: [{}], Error msg: {}", tenantId, edge.getId(), sessionId, msg.getDownlinkMsgId(), msg.getErrorMsg()); + DownlinkMsg downlinkMsg = sessionState.getPendingMsgsMap().get(msg.getDownlinkMsgId()); + // if NOT timeseries or attributes failures - ack failed downlink + if (downlinkMsg.getEntityDataCount() == 0) { + sessionState.getPendingMsgsMap().remove(msg.getDownlinkMsgId()); + } } if (sessionState.getPendingMsgsMap().isEmpty()) { log.debug("[{}][{}][{}] Pending msgs map is empty. Stopping current iteration", tenantId, edge.getId(), sessionId); @@ -768,41 +771,14 @@ public abstract class EdgeGrpcSession implements Closeable { } } - private DownlinkMsg convertEntityEventToDownlink(EdgeEvent edgeEvent) { - log.trace("[{}][{}] Executing convertEntityEventToDownlink, edgeEvent [{}], action [{}]", tenantId, sessionId, edgeEvent, edgeEvent.getAction()); - return switch (edgeEvent.getType()) { - case EDGE -> ctx.getEdgeProcessor().convertEdgeEventToDownlink(edgeEvent); - case DEVICE -> ctx.getDeviceProcessor().convertDeviceEventToDownlink(edgeEvent, edgeVersion); - case DEVICE_PROFILE -> ctx.getDeviceProfileProcessor().convertDeviceProfileEventToDownlink(edgeEvent, edgeVersion); - case ASSET_PROFILE -> ctx.getAssetProfileProcessor().convertAssetProfileEventToDownlink(edgeEvent, edgeVersion); - case ASSET -> ctx.getAssetProcessor().convertAssetEventToDownlink(edgeEvent, edgeVersion); - case ENTITY_VIEW -> ctx.getEntityViewProcessor().convertEntityViewEventToDownlink(edgeEvent, edgeVersion); - case DASHBOARD -> ctx.getDashboardProcessor().convertDashboardEventToDownlink(edgeEvent, edgeVersion); - case CUSTOMER -> ctx.getCustomerProcessor().convertCustomerEventToDownlink(edgeEvent, edgeVersion); - case RULE_CHAIN -> ctx.getRuleChainProcessor().convertRuleChainEventToDownlink(edgeEvent, edgeVersion); - case RULE_CHAIN_METADATA -> ctx.getRuleChainProcessor().convertRuleChainMetadataEventToDownlink(edgeEvent, edgeVersion); - case ALARM -> ctx.getAlarmProcessor().convertAlarmEventToDownlink(edgeEvent, edgeVersion); - case ALARM_COMMENT -> ctx.getAlarmProcessor().convertAlarmCommentEventToDownlink(edgeEvent, edgeVersion); - case USER -> ctx.getUserProcessor().convertUserEventToDownlink(edgeEvent, edgeVersion); - case RELATION -> ctx.getRelationProcessor().convertRelationEventToDownlink(edgeEvent, edgeVersion); - case WIDGETS_BUNDLE -> ctx.getWidgetBundleProcessor().convertWidgetsBundleEventToDownlink(edgeEvent, edgeVersion); - case WIDGET_TYPE -> ctx.getWidgetTypeProcessor().convertWidgetTypeEventToDownlink(edgeEvent, edgeVersion); - case ADMIN_SETTINGS -> ctx.getAdminSettingsProcessor().convertAdminSettingsEventToDownlink(edgeEvent, edgeVersion); - case OTA_PACKAGE -> ctx.getOtaPackageProcessor().convertOtaPackageEventToDownlink(edgeEvent, edgeVersion); - case TB_RESOURCE -> ctx.getResourceProcessor().convertResourceEventToDownlink(edgeEvent, edgeVersion); - case QUEUE -> ctx.getQueueProcessor().convertQueueEventToDownlink(edgeEvent, edgeVersion); - case TENANT -> ctx.getTenantProcessor().convertTenantEventToDownlink(edgeEvent, edgeVersion); - case TENANT_PROFILE -> ctx.getTenantProfileProcessor().convertTenantProfileEventToDownlink(edgeEvent, edgeVersion); - case NOTIFICATION_RULE -> ctx.getNotificationEdgeProcessor().convertNotificationRuleToDownlink(edgeEvent); - case NOTIFICATION_TARGET -> ctx.getNotificationEdgeProcessor().convertNotificationTargetToDownlink(edgeEvent); - case NOTIFICATION_TEMPLATE -> ctx.getNotificationEdgeProcessor().convertNotificationTemplateToDownlink(edgeEvent); - case OAUTH2_CLIENT -> ctx.getOAuth2EdgeProcessor().convertOAuth2ClientEventToDownlink(edgeEvent, edgeVersion); - case DOMAIN -> ctx.getOAuth2EdgeProcessor().convertOAuth2DomainEventToDownlink(edgeEvent, edgeVersion); - default -> { - log.warn("[{}][{}] Unsupported edge event type [{}]", tenantId, sessionId, edgeEvent); - yield null; - } - }; + protected DownlinkMsg convertEntityEventToDownlink(EdgeEvent edgeEvent) { + log.trace("[{}] Executing convertEntityEventToDownlink, edgeEvent [{}], action [{}]", edgeEvent.getTenantId(), edgeEvent, edgeEvent.getAction()); + if ((EdgeEventType.OAUTH2_CLIENT.equals(edgeEvent.getType()) || EdgeEventType.DOMAIN.equals(edgeEvent.getType())) && + (EdgeVersionUtils.isEdgeVersionOlderThan(edgeVersion, EdgeVersion.V_3_8_0))) { + return null; + } + + return ctx.getProcessor(edgeEvent.getType()).convertEdgeEventToDownlink(edgeEvent); } public void addEventToHighPriorityQueue(EdgeEvent edgeEvent) { @@ -821,38 +797,32 @@ public abstract class EdgeGrpcSession implements Closeable { try { if (uplinkMsg.getDeviceProfileUpdateMsgCount() > 0) { for (DeviceProfileUpdateMsg deviceProfileUpdateMsg : uplinkMsg.getDeviceProfileUpdateMsgList()) { - result.add(((DeviceProfileProcessor) ctx.getDeviceProfileEdgeProcessorFactory().getProcessorByEdgeVersion(edgeVersion)) - .processDeviceProfileMsgFromEdge(edge.getTenantId(), edge, deviceProfileUpdateMsg)); + result.add(ctx.getDeviceProfileProcessor().processDeviceProfileMsgFromEdge(edge.getTenantId(), edge, deviceProfileUpdateMsg)); } } if (uplinkMsg.getDeviceUpdateMsgCount() > 0) { for (DeviceUpdateMsg deviceUpdateMsg : uplinkMsg.getDeviceUpdateMsgList()) { - result.add(((DeviceProcessor) ctx.getDeviceEdgeProcessorFactory().getProcessorByEdgeVersion(edgeVersion)) - .processDeviceMsgFromEdge(edge.getTenantId(), edge, deviceUpdateMsg)); + result.add(ctx.getDeviceProcessor().processDeviceMsgFromEdge(edge.getTenantId(), edge, deviceUpdateMsg)); } } if (uplinkMsg.getDeviceCredentialsUpdateMsgCount() > 0) { for (DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg : uplinkMsg.getDeviceCredentialsUpdateMsgList()) { - result.add(((DeviceProcessor) ctx.getDeviceEdgeProcessorFactory().getProcessorByEdgeVersion(edgeVersion)) - .processDeviceCredentialsMsgFromEdge(edge.getTenantId(), edge.getId(), deviceCredentialsUpdateMsg)); + result.add(ctx.getDeviceProcessor().processDeviceCredentialsMsgFromEdge(edge.getTenantId(), edge.getId(), deviceCredentialsUpdateMsg)); } } if (uplinkMsg.getAssetProfileUpdateMsgCount() > 0) { for (AssetProfileUpdateMsg assetProfileUpdateMsg : uplinkMsg.getAssetProfileUpdateMsgList()) { - result.add(((AssetProfileProcessor) ctx.getAssetProfileEdgeProcessorFactory().getProcessorByEdgeVersion(edgeVersion)) - .processAssetProfileMsgFromEdge(edge.getTenantId(), edge, assetProfileUpdateMsg)); + result.add(ctx.getAssetProfileProcessor().processAssetProfileMsgFromEdge(edge.getTenantId(), edge, assetProfileUpdateMsg)); } } if (uplinkMsg.getAssetUpdateMsgCount() > 0) { for (AssetUpdateMsg assetUpdateMsg : uplinkMsg.getAssetUpdateMsgList()) { - result.add(((AssetProcessor) ctx.getAssetEdgeProcessorFactory().getProcessorByEdgeVersion(edgeVersion)) - .processAssetMsgFromEdge(edge.getTenantId(), edge, assetUpdateMsg)); + result.add(ctx.getAssetProcessor().processAssetMsgFromEdge(edge.getTenantId(), edge, assetUpdateMsg)); } } if (uplinkMsg.getEntityViewUpdateMsgCount() > 0) { for (EntityViewUpdateMsg entityViewUpdateMsg : uplinkMsg.getEntityViewUpdateMsgList()) { - result.add(((EntityViewProcessor) ctx.getEntityViewProcessorFactory().getProcessorByEdgeVersion(edgeVersion)) - .processEntityViewMsgFromEdge(edge.getTenantId(), edge, entityViewUpdateMsg)); + result.add(ctx.getEntityViewProcessor().processEntityViewMsgFromEdge(edge.getTenantId(), edge, entityViewUpdateMsg)); } } if (uplinkMsg.getEntityDataCount() > 0) { @@ -862,32 +832,27 @@ public abstract class EdgeGrpcSession implements Closeable { } if (uplinkMsg.getAlarmUpdateMsgCount() > 0) { for (AlarmUpdateMsg alarmUpdateMsg : uplinkMsg.getAlarmUpdateMsgList()) { - result.add(((AlarmProcessor) ctx.getAlarmEdgeProcessorFactory().getProcessorByEdgeVersion(edgeVersion)) - .processAlarmMsgFromEdge(edge.getTenantId(), edge.getId(), alarmUpdateMsg)); + result.add(ctx.getAlarmProcessor().processAlarmMsgFromEdge(edge.getTenantId(), edge.getId(), alarmUpdateMsg)); } } if (uplinkMsg.getAlarmCommentUpdateMsgCount() > 0) { for (AlarmCommentUpdateMsg alarmCommentUpdateMsg : uplinkMsg.getAlarmCommentUpdateMsgList()) { - result.add(((AlarmProcessor) ctx.getAlarmEdgeProcessorFactory().getProcessorByEdgeVersion(edgeVersion)) - .processAlarmCommentMsgFromEdge(edge.getTenantId(), edge.getId(), alarmCommentUpdateMsg)); + result.add(ctx.getAlarmCommentProcessor().processAlarmCommentMsgFromEdge(edge.getTenantId(), edge.getId(), alarmCommentUpdateMsg)); } } if (uplinkMsg.getRelationUpdateMsgCount() > 0) { for (RelationUpdateMsg relationUpdateMsg : uplinkMsg.getRelationUpdateMsgList()) { - result.add(((RelationProcessor) ctx.getRelationEdgeProcessorFactory().getProcessorByEdgeVersion(edgeVersion)) - .processRelationMsgFromEdge(edge.getTenantId(), edge, relationUpdateMsg)); + result.add(ctx.getRelationProcessor().processRelationMsgFromEdge(edge.getTenantId(), edge, relationUpdateMsg)); } } if (uplinkMsg.getDashboardUpdateMsgCount() > 0) { for (DashboardUpdateMsg dashboardUpdateMsg : uplinkMsg.getDashboardUpdateMsgList()) { - result.add(((DashboardProcessor) ctx.getDashboardEdgeProcessorFactory().getProcessorByEdgeVersion(edgeVersion)) - .processDashboardMsgFromEdge(edge.getTenantId(), edge, dashboardUpdateMsg)); + result.add(ctx.getDashboardProcessor().processDashboardMsgFromEdge(edge.getTenantId(), edge, dashboardUpdateMsg)); } } if (uplinkMsg.getResourceUpdateMsgCount() > 0) { for (ResourceUpdateMsg resourceUpdateMsg : uplinkMsg.getResourceUpdateMsgList()) { - result.add(((ResourceProcessor) ctx.getResourceEdgeProcessorFactory().getProcessorByEdgeVersion(edgeVersion)) - .processResourceMsgFromEdge(edge.getTenantId(), edge, resourceUpdateMsg)); + result.add(ctx.getResourceProcessor().processResourceMsgFromEdge(edge.getTenantId(), edge, resourceUpdateMsg)); } } if (uplinkMsg.getRuleChainMetadataRequestMsgCount() > 0) { @@ -917,8 +882,7 @@ public abstract class EdgeGrpcSession implements Closeable { } if (uplinkMsg.getDeviceRpcCallMsgCount() > 0) { for (DeviceRpcCallMsg deviceRpcCallMsg : uplinkMsg.getDeviceRpcCallMsgList()) { - result.add(((DeviceProcessor) ctx.getDeviceEdgeProcessorFactory().getProcessorByEdgeVersion(edgeVersion)) - .processDeviceRpcCallFromEdge(edge.getTenantId(), edge, deviceRpcCallMsg)); + result.add(ctx.getDeviceProcessor().processDeviceRpcCallFromEdge(edge.getTenantId(), edge, deviceRpcCallMsg)); } } if (uplinkMsg.getWidgetBundleTypesRequestMsgCount() > 0) { @@ -933,8 +897,8 @@ public abstract class EdgeGrpcSession implements Closeable { } } catch (Exception e) { String failureMsg = String.format("Can't process uplink msg [%s] from edge", uplinkMsg); - log.error("[{}][{}] Can't process uplink msg [{}]", tenantId, sessionId, uplinkMsg, e); - ctx.getNotificationRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(tenantId).edgeId(edge.getId()) + log.error("[{}][{}] Can't process uplink msg [{}]", edge.getTenantId(), sessionId, uplinkMsg, e); + ctx.getRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(edge.getTenantId()).edgeId(edge.getId()) .customerId(edge.getCustomerId()).edgeName(edge.getName()).failureMsg(failureMsg).error(e.getMessage()).build()); return Futures.immediateFailedFuture(e); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java index 7426207a56..7fab0094b4 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java @@ -87,6 +87,7 @@ public class EdgeSyncCursor { fetchers.add(new SystemWidgetsBundlesEdgeEventFetcher(ctx.getWidgetsBundleService())); fetchers.add(new TenantWidgetsBundlesEdgeEventFetcher(ctx.getWidgetsBundleService())); fetchers.add(new OtaPackagesEdgeEventFetcher(ctx.getOtaPackageService())); + fetchers.add(new DeviceProfilesEdgeEventFetcher(ctx.getDeviceProfileService())); fetchers.add(new TenantResourcesEdgeEventFetcher(ctx.getResourceService())); fetchers.add(new OAuth2EdgeEventFetcher(ctx.getDomainService())); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/BaseMsgConstructorFactory.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/BaseMsgConstructorFactory.java deleted file mode 100644 index dd76f97d8f..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/BaseMsgConstructorFactory.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Component -@TbCoreComponent -public abstract class BaseMsgConstructorFactory { - - @Autowired - protected T v1Constructor; - - @Autowired - protected U v2Constructor; - - public MsgConstructor getMsgConstructorByEdgeVersion(EdgeVersion edgeVersion) { - return switch (edgeVersion) { - case V_3_3_0, V_3_3_3, V_3_4_0, V_3_6_0, V_3_6_1 -> v1Constructor; - default -> v2Constructor; - }; - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/alarm/AlarmMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/alarm/AlarmMsgConstructor.java deleted file mode 100644 index b5a0f20d6b..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/alarm/AlarmMsgConstructor.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.alarm; - -import org.thingsboard.server.common.data.alarm.Alarm; -import org.thingsboard.server.common.data.alarm.AlarmComment; -import org.thingsboard.server.gen.edge.v1.AlarmCommentUpdateMsg; -import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.service.edge.rpc.constructor.MsgConstructor; - -public interface AlarmMsgConstructor extends MsgConstructor { - - AlarmUpdateMsg constructAlarmUpdatedMsg(UpdateMsgType msgType, Alarm alarm, String entityName); - - AlarmCommentUpdateMsg constructAlarmCommentUpdatedMsg(UpdateMsgType msgType, AlarmComment alarmComment); -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/alarm/AlarmMsgConstructorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/alarm/AlarmMsgConstructorV1.java deleted file mode 100644 index 81a489671f..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/alarm/AlarmMsgConstructorV1.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.alarm; - -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.alarm.Alarm; -import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Component -@TbCoreComponent -public class AlarmMsgConstructorV1 extends BaseAlarmMsgConstructor { - - @Override - public AlarmUpdateMsg constructAlarmUpdatedMsg(UpdateMsgType msgType, Alarm alarm, String entityName) { - return AlarmUpdateMsg.newBuilder() - .setMsgType(msgType) - .setIdMSB(alarm.getId().getId().getMostSignificantBits()) - .setIdLSB(alarm.getId().getId().getLeastSignificantBits()) - .setName(alarm.getName()) - .setType(alarm.getType()) - .setOriginatorName(entityName) - .setOriginatorType(alarm.getOriginator().getEntityType().name()) - .setSeverity(alarm.getSeverity().name()) - .setStatus(alarm.getStatus().name()) - .setStartTs(alarm.getStartTs()) - .setEndTs(alarm.getEndTs()) - .setAckTs(alarm.getAckTs()) - .setClearTs(alarm.getClearTs()) - .setDetails(JacksonUtil.toString(alarm.getDetails())) - .setPropagate(alarm.isPropagate()) - .setPropagateToOwner(alarm.isPropagateToOwner()) - .setPropagateToTenant(alarm.isPropagateToTenant()).build(); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/alarm/AlarmMsgConstructorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/alarm/AlarmMsgConstructorV2.java deleted file mode 100644 index 143a41b73c..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/alarm/AlarmMsgConstructorV2.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.alarm; - -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.alarm.Alarm; -import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Component -@TbCoreComponent -public class AlarmMsgConstructorV2 extends BaseAlarmMsgConstructor { - - @Override - public AlarmUpdateMsg constructAlarmUpdatedMsg(UpdateMsgType msgType, Alarm alarm, String entityName) { - return AlarmUpdateMsg.newBuilder().setMsgType(msgType) - .setEntity(JacksonUtil.toString(alarm)) - .setIdMSB(alarm.getId().getId().getMostSignificantBits()) - .setIdLSB(alarm.getId().getId().getLeastSignificantBits()).build(); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/asset/AssetMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/asset/AssetMsgConstructor.java deleted file mode 100644 index ba7b2a81ef..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/asset/AssetMsgConstructor.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.asset; - -import org.thingsboard.server.common.data.asset.Asset; -import org.thingsboard.server.common.data.asset.AssetProfile; -import org.thingsboard.server.common.data.id.AssetId; -import org.thingsboard.server.common.data.id.AssetProfileId; -import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg; -import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.service.edge.rpc.constructor.MsgConstructor; - -public interface AssetMsgConstructor extends MsgConstructor { - - AssetUpdateMsg constructAssetUpdatedMsg(UpdateMsgType msgType, Asset asset); - - AssetUpdateMsg constructAssetDeleteMsg(AssetId assetId); - - AssetProfileUpdateMsg constructAssetProfileUpdatedMsg(UpdateMsgType msgType, AssetProfile assetProfile); - - AssetProfileUpdateMsg constructAssetProfileDeleteMsg(AssetProfileId assetProfileId); -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/asset/AssetMsgConstructorFactory.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/asset/AssetMsgConstructorFactory.java deleted file mode 100644 index 1d236fce00..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/asset/AssetMsgConstructorFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.asset; - -import org.springframework.stereotype.Component; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.constructor.BaseMsgConstructorFactory; - -@Component -@TbCoreComponent -public class AssetMsgConstructorFactory extends BaseMsgConstructorFactory { - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/asset/AssetMsgConstructorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/asset/AssetMsgConstructorV1.java deleted file mode 100644 index 524edbb5a5..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/asset/AssetMsgConstructorV1.java +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.asset; - -import com.google.protobuf.ByteString; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.asset.Asset; -import org.thingsboard.server.common.data.asset.AssetProfile; -import org.thingsboard.server.dao.resource.ImageService; -import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg; -import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.queue.util.TbCoreComponent; - -import java.nio.charset.StandardCharsets; - -@Component -@TbCoreComponent -public class AssetMsgConstructorV1 extends BaseAssetMsgConstructor { - - @Autowired - private ImageService imageService; - - @Override - public AssetUpdateMsg constructAssetUpdatedMsg(UpdateMsgType msgType, Asset asset) { - AssetUpdateMsg.Builder builder = AssetUpdateMsg.newBuilder() - .setMsgType(msgType) - .setIdMSB(asset.getUuidId().getMostSignificantBits()) - .setIdLSB(asset.getUuidId().getLeastSignificantBits()) - .setName(asset.getName()) - .setType(asset.getType()); - if (asset.getLabel() != null) { - builder.setLabel(asset.getLabel()); - } - if (asset.getCustomerId() != null) { - builder.setCustomerIdMSB(asset.getCustomerId().getId().getMostSignificantBits()); - builder.setCustomerIdLSB(asset.getCustomerId().getId().getLeastSignificantBits()); - } - if (asset.getAssetProfileId() != null) { - builder.setAssetProfileIdMSB(asset.getAssetProfileId().getId().getMostSignificantBits()); - builder.setAssetProfileIdLSB(asset.getAssetProfileId().getId().getLeastSignificantBits()); - } - if (asset.getAdditionalInfo() != null) { - builder.setAdditionalInfo(JacksonUtil.toString(asset.getAdditionalInfo())); - } - return builder.build(); - } - - @Override - public AssetProfileUpdateMsg constructAssetProfileUpdatedMsg(UpdateMsgType msgType, AssetProfile assetProfile) { - assetProfile = JacksonUtil.clone(assetProfile); - imageService.inlineImageForEdge(assetProfile); - AssetProfileUpdateMsg.Builder builder = AssetProfileUpdateMsg.newBuilder() - .setMsgType(msgType) - .setIdMSB(assetProfile.getId().getId().getMostSignificantBits()) - .setIdLSB(assetProfile.getId().getId().getLeastSignificantBits()) - .setName(assetProfile.getName()) - .setDefault(assetProfile.isDefault()); - if (assetProfile.getDefaultDashboardId() != null) { - builder.setDefaultDashboardIdMSB(assetProfile.getDefaultDashboardId().getId().getMostSignificantBits()) - .setDefaultDashboardIdLSB(assetProfile.getDefaultDashboardId().getId().getLeastSignificantBits()); - } - if (assetProfile.getDefaultQueueName() != null) { - builder.setDefaultQueueName(assetProfile.getDefaultQueueName()); - } - if (assetProfile.getDescription() != null) { - builder.setDescription(assetProfile.getDescription()); - } - if (assetProfile.getImage() != null) { - builder.setImage(ByteString.copyFrom(assetProfile.getImage().getBytes(StandardCharsets.UTF_8))); - } - if (assetProfile.getDefaultEdgeRuleChainId() != null) { - builder.setDefaultRuleChainIdMSB(assetProfile.getDefaultEdgeRuleChainId().getId().getMostSignificantBits()) - .setDefaultRuleChainIdLSB(assetProfile.getDefaultEdgeRuleChainId().getId().getLeastSignificantBits()); - } - return builder.build(); - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/asset/AssetMsgConstructorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/asset/AssetMsgConstructorV2.java deleted file mode 100644 index b48cacb3ee..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/asset/AssetMsgConstructorV2.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.asset; - -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.asset.Asset; -import org.thingsboard.server.common.data.asset.AssetProfile; -import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg; -import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Component -@TbCoreComponent -public class AssetMsgConstructorV2 extends BaseAssetMsgConstructor { - - @Override - public AssetUpdateMsg constructAssetUpdatedMsg(UpdateMsgType msgType, Asset asset) { - return AssetUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(asset)) - .setIdMSB(asset.getUuidId().getMostSignificantBits()) - .setIdLSB(asset.getUuidId().getLeastSignificantBits()).build(); - } - - @Override - public AssetProfileUpdateMsg constructAssetProfileUpdatedMsg(UpdateMsgType msgType, AssetProfile assetProfile) { - return AssetProfileUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(assetProfile)) - .setIdMSB(assetProfile.getId().getId().getMostSignificantBits()) - .setIdLSB(assetProfile.getId().getId().getLeastSignificantBits()).build(); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/asset/BaseAssetMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/asset/BaseAssetMsgConstructor.java deleted file mode 100644 index a067d59ab4..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/asset/BaseAssetMsgConstructor.java +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.asset; - -import org.thingsboard.server.common.data.id.AssetId; -import org.thingsboard.server.common.data.id.AssetProfileId; -import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg; -import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; - -public abstract class BaseAssetMsgConstructor implements AssetMsgConstructor { - - @Override - public AssetUpdateMsg constructAssetDeleteMsg(AssetId assetId) { - return AssetUpdateMsg.newBuilder() - .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) - .setIdMSB(assetId.getId().getMostSignificantBits()) - .setIdLSB(assetId.getId().getLeastSignificantBits()).build(); - } - - @Override - public AssetProfileUpdateMsg constructAssetProfileDeleteMsg(AssetProfileId assetProfileId) { - return AssetProfileUpdateMsg.newBuilder() - .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) - .setIdMSB(assetProfileId.getId().getMostSignificantBits()) - .setIdLSB(assetProfileId.getId().getLeastSignificantBits()).build(); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/customer/BaseCustomerMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/customer/BaseCustomerMsgConstructor.java deleted file mode 100644 index 5e02200a16..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/customer/BaseCustomerMsgConstructor.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.customer; - -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.gen.edge.v1.CustomerUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; - -public abstract class BaseCustomerMsgConstructor implements CustomerMsgConstructor { - - @Override - public CustomerUpdateMsg constructCustomerDeleteMsg(CustomerId customerId) { - return CustomerUpdateMsg.newBuilder() - .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) - .setIdMSB(customerId.getId().getMostSignificantBits()) - .setIdLSB(customerId.getId().getLeastSignificantBits()).build(); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/customer/CustomerMsgConstructorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/customer/CustomerMsgConstructorV1.java deleted file mode 100644 index 1018997f47..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/customer/CustomerMsgConstructorV1.java +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.customer; - -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.Customer; -import org.thingsboard.server.gen.edge.v1.CustomerUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Component -@TbCoreComponent -public class CustomerMsgConstructorV1 extends BaseCustomerMsgConstructor { - - @Override - public CustomerUpdateMsg constructCustomerUpdatedMsg(UpdateMsgType msgType, Customer customer) { - CustomerUpdateMsg.Builder builder = CustomerUpdateMsg.newBuilder() - .setMsgType(msgType) - .setIdMSB(customer.getId().getId().getMostSignificantBits()) - .setIdLSB(customer.getId().getId().getLeastSignificantBits()) - .setTitle(customer.getTitle()); - if (customer.getCountry() != null) { - builder.setCountry(customer.getCountry()); - } - if (customer.getState() != null) { - builder.setState(customer.getState()); - } - if (customer.getCity() != null) { - builder.setCity(customer.getCity()); - } - if (customer.getAddress() != null) { - builder.setAddress(customer.getAddress()); - } - if (customer.getAddress2() != null) { - builder.setAddress2(customer.getAddress2()); - } - if (customer.getZip() != null) { - builder.setZip(customer.getZip()); - } - if (customer.getPhone() != null) { - builder.setPhone(customer.getPhone()); - } - if (customer.getEmail() != null) { - builder.setEmail(customer.getEmail()); - } - if (customer.getAdditionalInfo() != null) { - builder.setAdditionalInfo(JacksonUtil.toString(customer.getAdditionalInfo())); - } - return builder.build(); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/customer/CustomerMsgConstructorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/customer/CustomerMsgConstructorV2.java deleted file mode 100644 index 8f5a99f2ca..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/customer/CustomerMsgConstructorV2.java +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.customer; - -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.Customer; -import org.thingsboard.server.gen.edge.v1.CustomerUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Component -@TbCoreComponent -public class CustomerMsgConstructorV2 extends BaseCustomerMsgConstructor { - - @Override - public CustomerUpdateMsg constructCustomerUpdatedMsg(UpdateMsgType msgType, Customer customer) { - return CustomerUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(customer)) - .setIdMSB(customer.getId().getId().getMostSignificantBits()) - .setIdLSB(customer.getId().getId().getLeastSignificantBits()).build(); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/BaseDashboardMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/BaseDashboardMsgConstructor.java deleted file mode 100644 index f1314d9587..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/BaseDashboardMsgConstructor.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.dashboard; - -import org.thingsboard.server.common.data.id.DashboardId; -import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; - -public abstract class BaseDashboardMsgConstructor implements DashboardMsgConstructor { - - @Override - public DashboardUpdateMsg constructDashboardDeleteMsg(DashboardId dashboardId) { - return DashboardUpdateMsg.newBuilder() - .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) - .setIdMSB(dashboardId.getId().getMostSignificantBits()) - .setIdLSB(dashboardId.getId().getLeastSignificantBits()).build(); - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructor.java deleted file mode 100644 index bdf4968692..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructor.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.dashboard; - -import org.thingsboard.server.common.data.Dashboard; -import org.thingsboard.server.common.data.id.DashboardId; -import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.service.edge.rpc.constructor.MsgConstructor; - -public interface DashboardMsgConstructor extends MsgConstructor { - - DashboardUpdateMsg constructDashboardUpdatedMsg(UpdateMsgType msgType, Dashboard dashboard); - - DashboardUpdateMsg constructDashboardDeleteMsg(DashboardId dashboardId); - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructorV1.java deleted file mode 100644 index ec2d216a03..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructorV1.java +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.dashboard; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.Dashboard; -import org.thingsboard.server.dao.resource.ImageService; -import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Component -@TbCoreComponent -public class DashboardMsgConstructorV1 extends BaseDashboardMsgConstructor { - - @Autowired - private ImageService imageService; - - @Override - public DashboardUpdateMsg constructDashboardUpdatedMsg(UpdateMsgType msgType, Dashboard dashboard) { - dashboard = JacksonUtil.clone(dashboard); - imageService.inlineImagesForEdge(dashboard); - DashboardUpdateMsg.Builder builder = DashboardUpdateMsg.newBuilder() - .setMsgType(msgType) - .setIdMSB(dashboard.getId().getId().getMostSignificantBits()) - .setIdLSB(dashboard.getId().getId().getLeastSignificantBits()) - .setTitle(dashboard.getTitle()) - .setConfiguration(JacksonUtil.toString(dashboard.getConfiguration())) - .setMobileHide(dashboard.isMobileHide()); - if (dashboard.getAssignedCustomers() != null) { - builder.setAssignedCustomers(JacksonUtil.toString(dashboard.getAssignedCustomers())); - } - if (dashboard.getImage() != null) { - builder.setImage(dashboard.getImage()); - } - if (dashboard.getMobileOrder() != null) { - builder.setMobileOrder(dashboard.getMobileOrder()); - } - return builder.build(); - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructorV2.java deleted file mode 100644 index d1fb5ae703..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructorV2.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.dashboard; - -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.Dashboard; -import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Component -@TbCoreComponent -public class DashboardMsgConstructorV2 extends BaseDashboardMsgConstructor { - - @Override - public DashboardUpdateMsg constructDashboardUpdatedMsg(UpdateMsgType msgType, Dashboard dashboard) { - return DashboardUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(dashboard)) - .setIdMSB(dashboard.getId().getId().getMostSignificantBits()) - .setIdLSB(dashboard.getId().getId().getLeastSignificantBits()).build(); - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/BaseDeviceMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/BaseDeviceMsgConstructor.java deleted file mode 100644 index b875e1c947..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/BaseDeviceMsgConstructor.java +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.device; - -import com.fasterxml.jackson.databind.JsonNode; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.DeviceProfileId; -import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg; -import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg; -import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; -import org.thingsboard.server.gen.edge.v1.RpcRequestMsg; -import org.thingsboard.server.gen.edge.v1.RpcResponseMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; - -import java.util.UUID; - -public abstract class BaseDeviceMsgConstructor implements DeviceMsgConstructor { - - @Override - public DeviceUpdateMsg constructDeviceDeleteMsg(DeviceId deviceId) { - return DeviceUpdateMsg.newBuilder() - .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) - .setIdMSB(deviceId.getId().getMostSignificantBits()) - .setIdLSB(deviceId.getId().getLeastSignificantBits()).build(); - } - - @Override - public DeviceProfileUpdateMsg constructDeviceProfileDeleteMsg(DeviceProfileId deviceProfileId) { - return DeviceProfileUpdateMsg.newBuilder() - .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) - .setIdMSB(deviceProfileId.getId().getMostSignificantBits()) - .setIdLSB(deviceProfileId.getId().getLeastSignificantBits()).build(); - } - - @Override - public DeviceRpcCallMsg constructDeviceRpcCallMsg(UUID deviceId, JsonNode body) { - DeviceRpcCallMsg.Builder builder = constructDeviceRpcMsg(deviceId, body); - if (body.has("error") || body.has("response")) { - RpcResponseMsg.Builder responseBuilder = RpcResponseMsg.newBuilder(); - if (body.has("error")) { - responseBuilder.setError(body.get("error").asText()); - } else { - responseBuilder.setResponse(body.get("response").asText()); - } - builder.setResponseMsg(responseBuilder.build()); - } else { - RpcRequestMsg.Builder requestBuilder = RpcRequestMsg.newBuilder(); - requestBuilder.setMethod(body.get("method").asText()); - requestBuilder.setParams(body.get("params").asText()); - builder.setRequestMsg(requestBuilder.build()); - } - return builder.build(); - } - - private DeviceRpcCallMsg.Builder constructDeviceRpcMsg(UUID deviceId, JsonNode body) { - DeviceRpcCallMsg.Builder builder = DeviceRpcCallMsg.newBuilder() - .setDeviceIdMSB(deviceId.getMostSignificantBits()) - .setDeviceIdLSB(deviceId.getLeastSignificantBits()) - .setRequestId(body.get("requestId").asInt()); - if (body.get("oneway") != null) { - builder.setOneway(body.get("oneway").asBoolean()); - } - if (body.get("requestUUID") != null) { - UUID requestUUID = UUID.fromString(body.get("requestUUID").asText()); - builder.setRequestUuidMSB(requestUUID.getMostSignificantBits()) - .setRequestUuidLSB(requestUUID.getLeastSignificantBits()); - } - if (body.get("expirationTime") != null) { - builder.setExpirationTime(body.get("expirationTime").asLong()); - } - if (body.get("persisted") != null) { - builder.setPersisted(body.get("persisted").asBoolean()); - } - if (body.get("retries") != null) { - builder.setRetries(body.get("retries").asInt()); - } - if (body.get("additionalInfo") != null) { - builder.setAdditionalInfo(JacksonUtil.toString(body.get("additionalInfo"))); - } - if (body.get("serviceId") != null) { - builder.setServiceId(body.get("serviceId").asText()); - } - if (body.get("sessionId") != null) { - builder.setSessionId(body.get("sessionId").asText()); - } - return builder; - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructor.java deleted file mode 100644 index 854d0c80a8..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructor.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.device; - -import com.fasterxml.jackson.databind.JsonNode; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.DeviceProfileId; -import org.thingsboard.server.common.data.security.DeviceCredentials; -import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg; -import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg; -import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg; -import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.service.edge.rpc.constructor.MsgConstructor; - -import java.util.UUID; - -public interface DeviceMsgConstructor extends MsgConstructor { - - DeviceUpdateMsg constructDeviceUpdatedMsg(UpdateMsgType msgType, Device device); - - DeviceUpdateMsg constructDeviceDeleteMsg(DeviceId deviceId); - - DeviceCredentialsUpdateMsg constructDeviceCredentialsUpdatedMsg(DeviceCredentials deviceCredentials); - - DeviceProfileUpdateMsg constructDeviceProfileUpdatedMsg(UpdateMsgType msgType, DeviceProfile deviceProfile); - - DeviceProfileUpdateMsg constructDeviceProfileDeleteMsg(DeviceProfileId deviceProfileId); - - DeviceRpcCallMsg constructDeviceRpcCallMsg(UUID deviceId, JsonNode body); - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructorFactory.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructorFactory.java deleted file mode 100644 index 536e8db45b..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructorFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.device; - -import org.springframework.stereotype.Component; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.constructor.BaseMsgConstructorFactory; - -@Component -@TbCoreComponent -public class DeviceMsgConstructorFactory extends BaseMsgConstructorFactory { - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructorV1.java deleted file mode 100644 index 7b59bab3ba..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructorV1.java +++ /dev/null @@ -1,141 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.device; - -import com.google.protobuf.ByteString; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.security.DeviceCredentials; -import org.thingsboard.server.dao.resource.ImageService; -import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg; -import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg; -import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.queue.util.TbCoreComponent; - -import java.nio.charset.StandardCharsets; - -@Component -@TbCoreComponent -public class DeviceMsgConstructorV1 extends BaseDeviceMsgConstructor { - - @Autowired - private ImageService imageService; - - @Override - public DeviceUpdateMsg constructDeviceUpdatedMsg(UpdateMsgType msgType, Device device) { - DeviceUpdateMsg.Builder builder = DeviceUpdateMsg.newBuilder() - .setMsgType(msgType) - .setIdMSB(device.getId().getId().getMostSignificantBits()) - .setIdLSB(device.getId().getId().getLeastSignificantBits()) - .setName(device.getName()) - .setType(device.getType()); - if (device.getLabel() != null) { - builder.setLabel(device.getLabel()); - } - if (device.getCustomerId() != null) { - builder.setCustomerIdMSB(device.getCustomerId().getId().getMostSignificantBits()); - builder.setCustomerIdLSB(device.getCustomerId().getId().getLeastSignificantBits()); - } - if (device.getDeviceProfileId() != null) { - builder.setDeviceProfileIdMSB(device.getDeviceProfileId().getId().getMostSignificantBits()); - builder.setDeviceProfileIdLSB(device.getDeviceProfileId().getId().getLeastSignificantBits()); - } - if (device.getAdditionalInfo() != null) { - builder.setAdditionalInfo(JacksonUtil.toString(device.getAdditionalInfo())); - } - if (device.getFirmwareId() != null) { - builder.setFirmwareIdMSB(device.getFirmwareId().getId().getMostSignificantBits()) - .setFirmwareIdLSB(device.getFirmwareId().getId().getLeastSignificantBits()); - } - if (device.getSoftwareId() != null) { - builder.setSoftwareIdMSB(device.getSoftwareId().getId().getMostSignificantBits()) - .setSoftwareIdLSB(device.getSoftwareId().getId().getLeastSignificantBits()); - } - if (device.getDeviceData() != null) { - builder.setDeviceDataBytes(ByteString.copyFrom(device.getDeviceDataBytes())); - } - return builder.build(); - } - - @Override - public DeviceCredentialsUpdateMsg constructDeviceCredentialsUpdatedMsg(DeviceCredentials deviceCredentials) { - DeviceCredentialsUpdateMsg.Builder builder = DeviceCredentialsUpdateMsg.newBuilder() - .setDeviceIdMSB(deviceCredentials.getDeviceId().getId().getMostSignificantBits()) - .setDeviceIdLSB(deviceCredentials.getDeviceId().getId().getLeastSignificantBits()); - if (deviceCredentials.getCredentialsType() != null) { - builder.setCredentialsType(deviceCredentials.getCredentialsType().name()) - .setCredentialsId(deviceCredentials.getCredentialsId()); - } - if (deviceCredentials.getCredentialsValue() != null) { - builder.setCredentialsValue(deviceCredentials.getCredentialsValue()); - } - return builder.build(); - } - - @Override - public DeviceProfileUpdateMsg constructDeviceProfileUpdatedMsg(UpdateMsgType msgType, DeviceProfile deviceProfile) { - deviceProfile = JacksonUtil.clone(deviceProfile); - imageService.inlineImageForEdge(deviceProfile); - DeviceProfileUpdateMsg.Builder builder = DeviceProfileUpdateMsg.newBuilder() - .setMsgType(msgType) - .setIdMSB(deviceProfile.getId().getId().getMostSignificantBits()) - .setIdLSB(deviceProfile.getId().getId().getLeastSignificantBits()) - .setName(deviceProfile.getName()) - .setDefault(deviceProfile.isDefault()) - .setType(deviceProfile.getType().name()) - .setProfileDataBytes(ByteString.copyFrom(deviceProfile.getProfileDataBytes())); - if (deviceProfile.getDefaultQueueName() != null) { - builder.setDefaultQueueName(deviceProfile.getDefaultQueueName()); - } - if (deviceProfile.getDescription() != null) { - builder.setDescription(deviceProfile.getDescription()); - } - if (deviceProfile.getTransportType() != null) { - builder.setTransportType(deviceProfile.getTransportType().name()); - } - if (deviceProfile.getProvisionType() != null) { - builder.setProvisionType(deviceProfile.getProvisionType().name()); - } - if (deviceProfile.getProvisionDeviceKey() != null) { - builder.setProvisionDeviceKey(deviceProfile.getProvisionDeviceKey()); - } - if (deviceProfile.getImage() != null) { - builder.setImage(ByteString.copyFrom(deviceProfile.getImage().getBytes(StandardCharsets.UTF_8))); - } - if (deviceProfile.getFirmwareId() != null) { - builder.setFirmwareIdMSB(deviceProfile.getFirmwareId().getId().getMostSignificantBits()) - .setFirmwareIdLSB(deviceProfile.getFirmwareId().getId().getLeastSignificantBits()); - } - if (deviceProfile.getSoftwareId() != null) { - builder.setSoftwareIdMSB(deviceProfile.getSoftwareId().getId().getMostSignificantBits()) - .setSoftwareIdLSB(deviceProfile.getSoftwareId().getId().getLeastSignificantBits()); - } - if (deviceProfile.getDefaultEdgeRuleChainId() != null) { - builder.setDefaultRuleChainIdMSB(deviceProfile.getDefaultEdgeRuleChainId().getId().getMostSignificantBits()) - .setDefaultRuleChainIdLSB(deviceProfile.getDefaultEdgeRuleChainId().getId().getLeastSignificantBits()); - } - if (deviceProfile.getDefaultDashboardId() != null) { - builder.setDefaultDashboardIdMSB(deviceProfile.getDefaultDashboardId().getId().getMostSignificantBits()) - .setDefaultDashboardIdLSB(deviceProfile.getDefaultDashboardId().getId().getLeastSignificantBits()); - } - return builder.build(); - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructorV2.java deleted file mode 100644 index cf951e39a9..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructorV2.java +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.device; - -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.security.DeviceCredentials; -import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg; -import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg; -import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Component -@TbCoreComponent -public class DeviceMsgConstructorV2 extends BaseDeviceMsgConstructor { - - @Override - public DeviceUpdateMsg constructDeviceUpdatedMsg(UpdateMsgType msgType, Device device) { - return DeviceUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(device)) - .setIdMSB(device.getId().getId().getMostSignificantBits()) - .setIdLSB(device.getId().getId().getLeastSignificantBits()).build(); - } - - @Override - public DeviceCredentialsUpdateMsg constructDeviceCredentialsUpdatedMsg(DeviceCredentials deviceCredentials) { - return DeviceCredentialsUpdateMsg.newBuilder().setEntity(JacksonUtil.toString(deviceCredentials)).build(); - } - - @Override - public DeviceProfileUpdateMsg constructDeviceProfileUpdatedMsg(UpdateMsgType msgType, DeviceProfile deviceProfile) { - return DeviceProfileUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(deviceProfile)) - .setIdMSB(deviceProfile.getId().getId().getMostSignificantBits()) - .setIdLSB(deviceProfile.getId().getId().getLeastSignificantBits()).build(); - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/edge/EdgeMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/edge/EdgeMsgConstructor.java deleted file mode 100644 index 4b45b0427f..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/edge/EdgeMsgConstructor.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.edge; - -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.gen.edge.v1.EdgeConfiguration; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Component -@TbCoreComponent -public class EdgeMsgConstructor { - - public EdgeConfiguration constructEdgeConfiguration(Edge edge) { - EdgeConfiguration.Builder builder = EdgeConfiguration.newBuilder() - .setEdgeIdMSB(edge.getId().getId().getMostSignificantBits()) - .setEdgeIdLSB(edge.getId().getId().getLeastSignificantBits()) - .setTenantIdMSB(edge.getTenantId().getId().getMostSignificantBits()) - .setTenantIdLSB(edge.getTenantId().getId().getLeastSignificantBits()) - .setName(edge.getName()) - .setType(edge.getType()) - .setRoutingKey(edge.getRoutingKey()) - .setSecret(edge.getSecret()) - .setAdditionalInfo(JacksonUtil.toString(edge.getAdditionalInfo())) - .setCloudType("CE"); - if (edge.getCustomerId() != null) { - builder.setCustomerIdMSB(edge.getCustomerId().getId().getMostSignificantBits()) - .setCustomerIdLSB(edge.getCustomerId().getId().getLeastSignificantBits()); - } - return builder.build(); - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/BaseEntityViewMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/BaseEntityViewMsgConstructor.java deleted file mode 100644 index 7fa4a03c3e..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/BaseEntityViewMsgConstructor.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.entityview; - -import org.thingsboard.server.common.data.id.EntityViewId; -import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; - -public abstract class BaseEntityViewMsgConstructor implements EntityViewMsgConstructor { - - @Override - public EntityViewUpdateMsg constructEntityViewDeleteMsg(EntityViewId entityViewId) { - return EntityViewUpdateMsg.newBuilder() - .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) - .setIdMSB(entityViewId.getId().getMostSignificantBits()) - .setIdLSB(entityViewId.getId().getLeastSignificantBits()).build(); - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/EntityViewMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/EntityViewMsgConstructor.java deleted file mode 100644 index bec1d416d3..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/EntityViewMsgConstructor.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.entityview; - -import org.thingsboard.server.common.data.EntityView; -import org.thingsboard.server.common.data.id.EntityViewId; -import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.service.edge.rpc.constructor.MsgConstructor; - -public interface EntityViewMsgConstructor extends MsgConstructor { - - EntityViewUpdateMsg constructEntityViewUpdatedMsg(UpdateMsgType msgType, EntityView entityView); - - EntityViewUpdateMsg constructEntityViewDeleteMsg(EntityViewId entityViewId); - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/EntityViewMsgConstructorFactory.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/EntityViewMsgConstructorFactory.java deleted file mode 100644 index 2754622a67..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/EntityViewMsgConstructorFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.entityview; - -import org.springframework.stereotype.Component; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.constructor.BaseMsgConstructorFactory; - -@Component -@TbCoreComponent -public class EntityViewMsgConstructorFactory extends BaseMsgConstructorFactory { - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/EntityViewMsgConstructorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/EntityViewMsgConstructorV1.java deleted file mode 100644 index 1b3bba22c0..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/EntityViewMsgConstructorV1.java +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.entityview; - -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.EntityView; -import org.thingsboard.server.gen.edge.v1.EdgeEntityType; -import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Component -@TbCoreComponent -public class EntityViewMsgConstructorV1 extends BaseEntityViewMsgConstructor { - - @Override - public EntityViewUpdateMsg constructEntityViewUpdatedMsg(UpdateMsgType msgType, EntityView entityView) { - EdgeEntityType edgeEntityType = checkEntityType(entityView.getEntityId().getEntityType()); - EntityViewUpdateMsg.Builder builder = EntityViewUpdateMsg.newBuilder() - .setMsgType(msgType) - .setIdMSB(entityView.getId().getId().getMostSignificantBits()) - .setIdLSB(entityView.getId().getId().getLeastSignificantBits()) - .setName(entityView.getName()) - .setType(entityView.getType()) - .setEntityIdMSB(entityView.getEntityId().getId().getMostSignificantBits()) - .setEntityIdLSB(entityView.getEntityId().getId().getLeastSignificantBits()) - .setEntityType(edgeEntityType); - if (entityView.getCustomerId() != null) { - builder.setCustomerIdMSB(entityView.getCustomerId().getId().getMostSignificantBits()); - builder.setCustomerIdLSB(entityView.getCustomerId().getId().getLeastSignificantBits()); - } - if (entityView.getAdditionalInfo() != null) { - builder.setAdditionalInfo(JacksonUtil.toString(entityView.getAdditionalInfo())); - } - return builder.build(); - } - - private EdgeEntityType checkEntityType(EntityType entityType) { - return switch (entityType) { - case DEVICE -> EdgeEntityType.DEVICE; - case ASSET -> EdgeEntityType.ASSET; - default -> throw new RuntimeException("Unsupported entity type [" + entityType + "]"); - }; - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/EntityViewMsgConstructorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/EntityViewMsgConstructorV2.java deleted file mode 100644 index 6db4818688..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/EntityViewMsgConstructorV2.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.entityview; - -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.EntityView; -import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Component -@TbCoreComponent -public class EntityViewMsgConstructorV2 extends BaseEntityViewMsgConstructor { - - @Override - public EntityViewUpdateMsg constructEntityViewUpdatedMsg(UpdateMsgType msgType, EntityView entityView) { - return EntityViewUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(entityView)) - .setIdMSB(entityView.getId().getId().getMostSignificantBits()) - .setIdLSB(entityView.getId().getId().getLeastSignificantBits()).build(); - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/notification/NotificationMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/notification/NotificationMsgConstructor.java deleted file mode 100644 index 9c8f5a37e6..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/notification/NotificationMsgConstructor.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.notification; - -import org.thingsboard.server.common.data.id.NotificationRuleId; -import org.thingsboard.server.common.data.id.NotificationTargetId; -import org.thingsboard.server.common.data.id.NotificationTemplateId; -import org.thingsboard.server.common.data.notification.rule.NotificationRule; -import org.thingsboard.server.common.data.notification.targets.NotificationTarget; -import org.thingsboard.server.common.data.notification.template.NotificationTemplate; -import org.thingsboard.server.gen.edge.v1.NotificationRuleUpdateMsg; -import org.thingsboard.server.gen.edge.v1.NotificationTargetUpdateMsg; -import org.thingsboard.server.gen.edge.v1.NotificationTemplateUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; - -public interface NotificationMsgConstructor { - - NotificationRuleUpdateMsg constructNotificationRuleUpdateMsg(UpdateMsgType msgType, NotificationRule notificationRule); - - NotificationRuleUpdateMsg constructNotificationRuleDeleteMsg(NotificationRuleId notificationRuleId); - - NotificationTargetUpdateMsg constructNotificationTargetUpdateMsg(UpdateMsgType msgType, NotificationTarget notificationTarget); - - NotificationTargetUpdateMsg constructNotificationTargetDeleteMsg(NotificationTargetId notificationTargetId); - - NotificationTemplateUpdateMsg constructNotificationTemplateUpdateMsg(UpdateMsgType msgType, NotificationTemplate notificationTemplate); - - NotificationTemplateUpdateMsg constructNotificationTemplateDeleteMsg(NotificationTemplateId notificationTemplateId); - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/notification/NotificationMsgConstructorImpl.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/notification/NotificationMsgConstructorImpl.java deleted file mode 100644 index 8340bee693..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/notification/NotificationMsgConstructorImpl.java +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.notification; - -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.id.NotificationRuleId; -import org.thingsboard.server.common.data.id.NotificationTargetId; -import org.thingsboard.server.common.data.id.NotificationTemplateId; -import org.thingsboard.server.common.data.notification.rule.NotificationRule; -import org.thingsboard.server.common.data.notification.targets.NotificationTarget; -import org.thingsboard.server.common.data.notification.template.NotificationTemplate; -import org.thingsboard.server.gen.edge.v1.NotificationRuleUpdateMsg; -import org.thingsboard.server.gen.edge.v1.NotificationTargetUpdateMsg; -import org.thingsboard.server.gen.edge.v1.NotificationTemplateUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Component -@TbCoreComponent -public class NotificationMsgConstructorImpl implements NotificationMsgConstructor { - - @Override - public NotificationRuleUpdateMsg constructNotificationRuleUpdateMsg(UpdateMsgType msgType, NotificationRule notificationRule) { - return NotificationRuleUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(notificationRule)).build(); - } - - @Override - public NotificationRuleUpdateMsg constructNotificationRuleDeleteMsg(NotificationRuleId notificationRuleId) { - return NotificationRuleUpdateMsg.newBuilder() - .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) - .setIdMSB(notificationRuleId.getId().getMostSignificantBits()) - .setIdLSB(notificationRuleId.getId().getLeastSignificantBits()).build(); - } - - @Override - public NotificationTargetUpdateMsg constructNotificationTargetUpdateMsg(UpdateMsgType msgType, NotificationTarget notificationTarget) { - return NotificationTargetUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(notificationTarget)).build(); - } - - @Override - public NotificationTargetUpdateMsg constructNotificationTargetDeleteMsg(NotificationTargetId notificationTargetId) { - return NotificationTargetUpdateMsg.newBuilder() - .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) - .setIdMSB(notificationTargetId.getId().getMostSignificantBits()) - .setIdLSB(notificationTargetId.getId().getLeastSignificantBits()).build(); - } - - @Override - public NotificationTemplateUpdateMsg constructNotificationTemplateUpdateMsg(UpdateMsgType msgType, NotificationTemplate notificationTemplate) { - return NotificationTemplateUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(notificationTemplate)).build(); - } - - @Override - public NotificationTemplateUpdateMsg constructNotificationTemplateDeleteMsg(NotificationTemplateId notificationTemplateId) { - return NotificationTemplateUpdateMsg.newBuilder() - .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) - .setIdMSB(notificationTemplateId.getId().getMostSignificantBits()) - .setIdLSB(notificationTemplateId.getId().getLeastSignificantBits()).build(); - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/oauth2/OAuth2MsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/oauth2/OAuth2MsgConstructor.java deleted file mode 100644 index 7ed25924b6..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/oauth2/OAuth2MsgConstructor.java +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.oauth2; - -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.domain.DomainInfo; -import org.thingsboard.server.common.data.id.DomainId; -import org.thingsboard.server.common.data.id.OAuth2ClientId; -import org.thingsboard.server.common.data.oauth2.OAuth2Client; -import org.thingsboard.server.gen.edge.v1.OAuth2ClientUpdateMsg; -import org.thingsboard.server.gen.edge.v1.OAuth2DomainUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Component -@TbCoreComponent -public class OAuth2MsgConstructor { - - public OAuth2ClientUpdateMsg constructOAuth2ClientUpdateMsg(UpdateMsgType msgType, OAuth2Client oAuth2Client) { - return OAuth2ClientUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(oAuth2Client)) - .setIdMSB(oAuth2Client.getId().getId().getMostSignificantBits()) - .setIdLSB(oAuth2Client.getId().getId().getLeastSignificantBits()).build(); - } - - public OAuth2ClientUpdateMsg constructOAuth2ClientDeleteMsg(OAuth2ClientId oAuth2ClientId) { - return OAuth2ClientUpdateMsg.newBuilder() - .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) - .setIdMSB(oAuth2ClientId.getId().getMostSignificantBits()) - .setIdLSB(oAuth2ClientId.getId().getLeastSignificantBits()).build(); - } - - public OAuth2DomainUpdateMsg constructOAuth2DomainUpdateMsg(UpdateMsgType msgType, DomainInfo domainInfo) { - return OAuth2DomainUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(domainInfo)) - .setIdMSB(domainInfo.getId().getId().getMostSignificantBits()) - .setIdLSB(domainInfo.getId().getId().getLeastSignificantBits()).build(); - } - - public OAuth2DomainUpdateMsg constructOAuth2DomainDeleteMsg(DomainId domainId) { - return OAuth2DomainUpdateMsg.newBuilder() - .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) - .setIdMSB(domainId.getId().getMostSignificantBits()) - .setIdLSB(domainId.getId().getLeastSignificantBits()) - .build(); - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/ota/BaseOtaPackageMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/ota/BaseOtaPackageMsgConstructor.java deleted file mode 100644 index c06d6adc4b..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/ota/BaseOtaPackageMsgConstructor.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.ota; - -import org.thingsboard.server.common.data.id.OtaPackageId; -import org.thingsboard.server.gen.edge.v1.OtaPackageUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; - -public abstract class BaseOtaPackageMsgConstructor implements OtaPackageMsgConstructor { - - @Override - public OtaPackageUpdateMsg constructOtaPackageDeleteMsg(OtaPackageId otaPackageId) { - return OtaPackageUpdateMsg.newBuilder() - .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) - .setIdMSB(otaPackageId.getId().getMostSignificantBits()) - .setIdLSB(otaPackageId.getId().getLeastSignificantBits()).build(); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/ota/OtaPackageMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/ota/OtaPackageMsgConstructor.java deleted file mode 100644 index 247043eb20..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/ota/OtaPackageMsgConstructor.java +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.ota; - -import org.thingsboard.server.common.data.OtaPackage; -import org.thingsboard.server.common.data.id.OtaPackageId; -import org.thingsboard.server.gen.edge.v1.OtaPackageUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.service.edge.rpc.constructor.MsgConstructor; - -public interface OtaPackageMsgConstructor extends MsgConstructor { - - OtaPackageUpdateMsg constructOtaPackageUpdatedMsg(UpdateMsgType msgType, OtaPackage otaPackage); - - OtaPackageUpdateMsg constructOtaPackageDeleteMsg(OtaPackageId otaPackageId); -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/ota/OtaPackageMsgConstructorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/ota/OtaPackageMsgConstructorV1.java deleted file mode 100644 index 3c743a4200..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/ota/OtaPackageMsgConstructorV1.java +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.ota; - -import com.google.protobuf.ByteString; -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.OtaPackage; -import org.thingsboard.server.gen.edge.v1.OtaPackageUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Component -@TbCoreComponent -public class OtaPackageMsgConstructorV1 extends BaseOtaPackageMsgConstructor { - - @Override - public OtaPackageUpdateMsg constructOtaPackageUpdatedMsg(UpdateMsgType msgType, OtaPackage otaPackage) { - OtaPackageUpdateMsg.Builder builder = OtaPackageUpdateMsg.newBuilder() - .setMsgType(msgType) - .setIdMSB(otaPackage.getId().getId().getMostSignificantBits()) - .setIdLSB(otaPackage.getId().getId().getLeastSignificantBits()) - .setType(otaPackage.getType().name()) - .setTitle(otaPackage.getTitle()) - .setVersion(otaPackage.getVersion()) - .setTag(otaPackage.getTag()); - - if (otaPackage.getDeviceProfileId() != null) { - builder.setDeviceProfileIdMSB(otaPackage.getDeviceProfileId().getId().getMostSignificantBits()) - .setDeviceProfileIdLSB(otaPackage.getDeviceProfileId().getId().getLeastSignificantBits()); - } - - if (otaPackage.getUrl() != null) { - builder.setUrl(otaPackage.getUrl()); - } - if (otaPackage.getAdditionalInfo() != null) { - builder.setAdditionalInfo(JacksonUtil.toString(otaPackage.getAdditionalInfo())); - } - if (otaPackage.getFileName() != null) { - builder.setFileName(otaPackage.getFileName()); - } - if (otaPackage.getContentType() != null) { - builder.setContentType(otaPackage.getContentType()); - } - if (otaPackage.getChecksumAlgorithm() != null) { - builder.setChecksumAlgorithm(otaPackage.getChecksumAlgorithm().name()); - } - if (otaPackage.getChecksum() != null) { - builder.setChecksum(otaPackage.getChecksum()); - } - if (otaPackage.getDataSize() != null) { - builder.setDataSize(otaPackage.getDataSize()); - } - if (otaPackage.getData() != null) { - builder.setData(ByteString.copyFrom(otaPackage.getData().array())); - } - return builder.build(); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/ota/OtaPackageMsgConstructorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/ota/OtaPackageMsgConstructorV2.java deleted file mode 100644 index c3c7eac9a4..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/ota/OtaPackageMsgConstructorV2.java +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.ota; - -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.OtaPackage; -import org.thingsboard.server.gen.edge.v1.OtaPackageUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Component -@TbCoreComponent -public class OtaPackageMsgConstructorV2 extends BaseOtaPackageMsgConstructor { - - @Override - public OtaPackageUpdateMsg constructOtaPackageUpdatedMsg(UpdateMsgType msgType, OtaPackage otaPackage) { - return OtaPackageUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(otaPackage)) - .setIdMSB(otaPackage.getId().getId().getMostSignificantBits()) - .setIdLSB(otaPackage.getId().getId().getLeastSignificantBits()).build(); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/queue/BaseQueueMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/queue/BaseQueueMsgConstructor.java deleted file mode 100644 index ce68ef8bcb..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/queue/BaseQueueMsgConstructor.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.queue; - -import org.thingsboard.server.common.data.id.QueueId; -import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; - -public abstract class BaseQueueMsgConstructor implements QueueMsgConstructor { - - @Override - public QueueUpdateMsg constructQueueDeleteMsg(QueueId queueId) { - return QueueUpdateMsg.newBuilder() - .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) - .setIdMSB(queueId.getId().getMostSignificantBits()) - .setIdLSB(queueId.getId().getLeastSignificantBits()).build(); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/queue/QueueMsgConstructorFactory.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/queue/QueueMsgConstructorFactory.java deleted file mode 100644 index 5549a2fdba..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/queue/QueueMsgConstructorFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.queue; - -import org.springframework.stereotype.Component; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.constructor.BaseMsgConstructorFactory; - -@Component -@TbCoreComponent -public class QueueMsgConstructorFactory extends BaseMsgConstructorFactory { - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/queue/QueueMsgConstructorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/queue/QueueMsgConstructorV1.java deleted file mode 100644 index 78cdc0f7e4..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/queue/QueueMsgConstructorV1.java +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.queue; - -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.id.QueueId; -import org.thingsboard.server.common.data.queue.ProcessingStrategy; -import org.thingsboard.server.common.data.queue.Queue; -import org.thingsboard.server.common.data.queue.SubmitStrategy; -import org.thingsboard.server.gen.edge.v1.ProcessingStrategyProto; -import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg; -import org.thingsboard.server.gen.edge.v1.SubmitStrategyProto; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Component -@TbCoreComponent -public class QueueMsgConstructorV1 extends BaseQueueMsgConstructor { - - @Override - public QueueUpdateMsg constructQueueUpdatedMsg(UpdateMsgType msgType, Queue queue) { - return QueueUpdateMsg.newBuilder() - .setMsgType(msgType) - .setIdMSB(queue.getId().getId().getMostSignificantBits()) - .setIdLSB(queue.getId().getId().getLeastSignificantBits()) - .setTenantIdMSB(queue.getTenantId().getId().getMostSignificantBits()) - .setTenantIdLSB(queue.getTenantId().getId().getLeastSignificantBits()) - .setName(queue.getName()) - .setTopic(queue.getTopic()) - .setPollInterval(queue.getPollInterval()) - .setPartitions(queue.getPartitions()) - .setConsumerPerPartition(queue.isConsumerPerPartition()) - .setPackProcessingTimeout(queue.getPackProcessingTimeout()) - .setSubmitStrategy(createSubmitStrategyProto(queue.getSubmitStrategy())) - .setProcessingStrategy(createProcessingStrategyProto(queue.getProcessingStrategy())).build(); - } - - private ProcessingStrategyProto createProcessingStrategyProto(ProcessingStrategy processingStrategy) { - return ProcessingStrategyProto.newBuilder() - .setType(processingStrategy.getType().name()) - .setRetries(processingStrategy.getRetries()) - .setFailurePercentage(processingStrategy.getFailurePercentage()) - .setPauseBetweenRetries(processingStrategy.getPauseBetweenRetries()) - .setMaxPauseBetweenRetries(processingStrategy.getMaxPauseBetweenRetries()) - .build(); - } - - private SubmitStrategyProto createSubmitStrategyProto(SubmitStrategy submitStrategy) { - return SubmitStrategyProto.newBuilder() - .setType(submitStrategy.getType().name()) - .setBatchSize(submitStrategy.getBatchSize()) - .build(); - } - - public QueueUpdateMsg constructQueueDeleteMsg(QueueId queueId) { - return QueueUpdateMsg.newBuilder() - .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) - .setIdMSB(queueId.getId().getMostSignificantBits()) - .setIdLSB(queueId.getId().getLeastSignificantBits()).build(); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/queue/QueueMsgConstructorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/queue/QueueMsgConstructorV2.java deleted file mode 100644 index 9df94b957e..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/queue/QueueMsgConstructorV2.java +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.queue; - -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.queue.Queue; -import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Component -@TbCoreComponent -public class QueueMsgConstructorV2 extends BaseQueueMsgConstructor { - - @Override - public QueueUpdateMsg constructQueueUpdatedMsg(UpdateMsgType msgType, Queue queue) { - return QueueUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(queue)) - .setIdMSB(queue.getId().getId().getMostSignificantBits()) - .setIdLSB(queue.getId().getId().getLeastSignificantBits()).build(); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/relation/RelationMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/relation/RelationMsgConstructor.java deleted file mode 100644 index 4dc1cd6777..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/relation/RelationMsgConstructor.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.relation; - -import org.thingsboard.server.common.data.relation.EntityRelation; -import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.service.edge.rpc.constructor.MsgConstructor; - -public interface RelationMsgConstructor extends MsgConstructor { - - RelationUpdateMsg constructRelationUpdatedMsg(UpdateMsgType msgType, EntityRelation entityRelation); -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/relation/RelationMsgConstructorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/relation/RelationMsgConstructorV1.java deleted file mode 100644 index f5b9ae59f3..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/relation/RelationMsgConstructorV1.java +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.relation; - -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.relation.EntityRelation; -import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Component -@TbCoreComponent -public class RelationMsgConstructorV1 implements RelationMsgConstructor { - - @Override - public RelationUpdateMsg constructRelationUpdatedMsg(UpdateMsgType msgType, EntityRelation entityRelation) { - RelationUpdateMsg.Builder builder = RelationUpdateMsg.newBuilder() - .setMsgType(msgType) - .setFromIdMSB(entityRelation.getFrom().getId().getMostSignificantBits()) - .setFromIdLSB(entityRelation.getFrom().getId().getLeastSignificantBits()) - .setFromEntityType(entityRelation.getFrom().getEntityType().name()) - .setToIdMSB(entityRelation.getTo().getId().getMostSignificantBits()) - .setToIdLSB(entityRelation.getTo().getId().getLeastSignificantBits()) - .setToEntityType(entityRelation.getTo().getEntityType().name()) - .setType(entityRelation.getType()); - if (entityRelation.getAdditionalInfo() != null) { - builder.setAdditionalInfo(JacksonUtil.toString(entityRelation.getAdditionalInfo())); - } - if (entityRelation.getTypeGroup() != null) { - builder.setTypeGroup(entityRelation.getTypeGroup().name()); - } - return builder.build(); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/relation/RelationMsgConstructorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/relation/RelationMsgConstructorV2.java deleted file mode 100644 index 9a52b0b29b..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/relation/RelationMsgConstructorV2.java +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.relation; - -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.relation.EntityRelation; -import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Component -@TbCoreComponent -public class RelationMsgConstructorV2 implements RelationMsgConstructor { - - - @Override - public RelationUpdateMsg constructRelationUpdatedMsg(UpdateMsgType msgType, EntityRelation entityRelation) { - return RelationUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(entityRelation)).build(); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/resource/BaseResourceMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/resource/BaseResourceMsgConstructor.java deleted file mode 100644 index 0a9d34fb85..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/resource/BaseResourceMsgConstructor.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.resource; - -import org.thingsboard.server.common.data.id.TbResourceId; -import org.thingsboard.server.gen.edge.v1.ResourceUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; - -public abstract class BaseResourceMsgConstructor implements ResourceMsgConstructor { - - @Override - public ResourceUpdateMsg constructResourceDeleteMsg(TbResourceId tbResourceId) { - return ResourceUpdateMsg.newBuilder() - .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) - .setIdMSB(tbResourceId.getId().getMostSignificantBits()) - .setIdLSB(tbResourceId.getId().getLeastSignificantBits()).build(); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/resource/ResourceMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/resource/ResourceMsgConstructor.java deleted file mode 100644 index 9b002237fb..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/resource/ResourceMsgConstructor.java +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.resource; - -import org.thingsboard.server.common.data.TbResource; -import org.thingsboard.server.common.data.id.TbResourceId; -import org.thingsboard.server.gen.edge.v1.ResourceUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.service.edge.rpc.constructor.MsgConstructor; - -public interface ResourceMsgConstructor extends MsgConstructor { - - ResourceUpdateMsg constructResourceUpdatedMsg(UpdateMsgType msgType, TbResource tbResource); - - ResourceUpdateMsg constructResourceDeleteMsg(TbResourceId tbResourceId); -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/resource/ResourceMsgConstructorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/resource/ResourceMsgConstructorV1.java deleted file mode 100644 index e1cf75ea56..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/resource/ResourceMsgConstructorV1.java +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.resource; - -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.ResourceType; -import org.thingsboard.server.common.data.TbResource; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.gen.edge.v1.ResourceUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Component -@TbCoreComponent -public class ResourceMsgConstructorV1 extends BaseResourceMsgConstructor { - - @Override - public ResourceUpdateMsg constructResourceUpdatedMsg(UpdateMsgType msgType, TbResource tbResource) { - if (ResourceType.IMAGE.equals(tbResource.getResourceType())) { - // Exclude support for a recently added resource type when dealing with older Edges - // to maintain compatibility and avoid potential issues. - return null; - } - ResourceUpdateMsg.Builder builder = ResourceUpdateMsg.newBuilder() - .setMsgType(msgType) - .setIdMSB(tbResource.getId().getId().getMostSignificantBits()) - .setIdLSB(tbResource.getId().getId().getLeastSignificantBits()) - .setTitle(tbResource.getTitle()) - .setResourceKey(tbResource.getResourceKey()) - .setResourceType(tbResource.getResourceType().name()) - .setFileName(tbResource.getFileName()); - if (tbResource.getData() != null) { - builder.setData(tbResource.getEncodedData()); - } - if (tbResource.getEtag() != null) { - builder.setEtag(tbResource.getEtag()); - } - if (TenantId.SYS_TENANT_ID.equals(tbResource.getTenantId())) { - builder.setIsSystem(true); - } - return builder.build(); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/resource/ResourceMsgConstructorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/resource/ResourceMsgConstructorV2.java deleted file mode 100644 index 85aaa61def..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/resource/ResourceMsgConstructorV2.java +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.resource; - -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.TbResource; -import org.thingsboard.server.gen.edge.v1.ResourceUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Component -@TbCoreComponent -public class ResourceMsgConstructorV2 extends BaseResourceMsgConstructor { - - @Override - public ResourceUpdateMsg constructResourceUpdatedMsg(UpdateMsgType msgType, TbResource tbResource) { - return ResourceUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(tbResource)) - .setIdMSB(tbResource.getId().getId().getMostSignificantBits()) - .setIdLSB(tbResource.getId().getId().getLeastSignificantBits()).build(); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/BaseRuleChainMetadataConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/BaseRuleChainMetadataConstructor.java deleted file mode 100644 index efa541ca54..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/BaseRuleChainMetadataConstructor.java +++ /dev/null @@ -1,133 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.rule; - -import com.fasterxml.jackson.databind.node.ObjectNode; -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.rule.NodeConnectionInfo; -import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo; -import org.thingsboard.server.common.data.rule.RuleChainMetaData; -import org.thingsboard.server.common.data.rule.RuleNode; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; -import org.thingsboard.server.gen.edge.v1.NodeConnectionInfoProto; -import org.thingsboard.server.gen.edge.v1.RuleChainConnectionInfoProto; -import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; -import org.thingsboard.server.gen.edge.v1.RuleNodeProto; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; - -import java.util.ArrayList; -import java.util.List; -import java.util.NavigableSet; - -@Slf4j -@AllArgsConstructor -public abstract class BaseRuleChainMetadataConstructor implements RuleChainMetadataConstructor { - - @Override - public RuleChainMetadataUpdateMsg constructRuleChainMetadataUpdatedMsg(TenantId tenantId, - UpdateMsgType msgType, - RuleChainMetaData ruleChainMetaData, - EdgeVersion edgeVersion) { - RuleChainMetadataUpdateMsg.Builder builder = RuleChainMetadataUpdateMsg.newBuilder(); - constructRuleChainMetadataUpdatedMsg(tenantId, builder, ruleChainMetaData); - builder.setMsgType(msgType); - return builder.build(); - } - - protected abstract void constructRuleChainMetadataUpdatedMsg(TenantId tenantId, - RuleChainMetadataUpdateMsg.Builder builder, - RuleChainMetaData ruleChainMetaData); - - protected List constructConnections(List connections) { - List result = new ArrayList<>(); - if (connections != null && !connections.isEmpty()) { - for (NodeConnectionInfo connection : connections) { - result.add(constructConnection(connection)); - } - } - return result; - } - - private NodeConnectionInfoProto constructConnection(NodeConnectionInfo connection) { - return NodeConnectionInfoProto.newBuilder() - .setFromIndex(connection.getFromIndex()) - .setToIndex(connection.getToIndex()) - .setType(connection.getType()) - .build(); - } - - protected List constructNodes(List nodes) { - List result = new ArrayList<>(); - if (nodes != null && !nodes.isEmpty()) { - for (RuleNode node : nodes) { - result.add(constructNode(node)); - } - } - return result; - } - - private RuleNodeProto constructNode(RuleNode node) { - return RuleNodeProto.newBuilder() - .setIdMSB(node.getId().getId().getMostSignificantBits()) - .setIdLSB(node.getId().getId().getLeastSignificantBits()) - .setType(node.getType()) - .setName(node.getName()) - .setDebugSettings(JacksonUtil.toString(node.getDebugSettings())) - .setConfiguration(JacksonUtil.toString(node.getConfiguration())) - .setAdditionalInfo(JacksonUtil.toString(node.getAdditionalInfo())) - .setSingletonMode(node.isSingletonMode()) - .setConfigurationVersion(node.getConfigurationVersion()) - .build(); - } - - protected List constructRuleChainConnections(List ruleChainConnections, - NavigableSet removedNodeIndexes) { - List result = new ArrayList<>(); - if (ruleChainConnections != null && !ruleChainConnections.isEmpty()) { - for (RuleChainConnectionInfo ruleChainConnectionInfo : ruleChainConnections) { - if (!removedNodeIndexes.isEmpty()) { // 3_3_0 only - int fromIndex = ruleChainConnectionInfo.getFromIndex(); - // decrease index because of removed nodes - for (Integer removedIndex : removedNodeIndexes) { - if (fromIndex > removedIndex) { - fromIndex = fromIndex - 1; - } - } - ruleChainConnectionInfo.setFromIndex(fromIndex); - ObjectNode additionalInfo = (ObjectNode) ruleChainConnectionInfo.getAdditionalInfo(); - if (additionalInfo.get("ruleChainNodeId") == null) { - additionalInfo.put("ruleChainNodeId", "rule-chain-node-UNDEFINED"); - } - } - result.add(constructRuleChainConnection(ruleChainConnectionInfo)); - } - } - return result; - } - - private RuleChainConnectionInfoProto constructRuleChainConnection(RuleChainConnectionInfo ruleChainConnectionInfo) { - return RuleChainConnectionInfoProto.newBuilder() - .setFromIndex(ruleChainConnectionInfo.getFromIndex()) - .setTargetRuleChainIdMSB(ruleChainConnectionInfo.getTargetRuleChainId().getId().getMostSignificantBits()) - .setTargetRuleChainIdLSB(ruleChainConnectionInfo.getTargetRuleChainId().getId().getLeastSignificantBits()) - .setType(ruleChainConnectionInfo.getType()) - .setAdditionalInfo(JacksonUtil.toString(ruleChainConnectionInfo.getAdditionalInfo())) - .build(); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/BaseRuleChainMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/BaseRuleChainMsgConstructor.java deleted file mode 100644 index 89ac4123a4..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/BaseRuleChainMsgConstructor.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.rule; - -import org.thingsboard.server.common.data.id.RuleChainId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.rule.RuleChainMetaData; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; -import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; -import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; - -public abstract class BaseRuleChainMsgConstructor implements RuleChainMsgConstructor { - - @Override - public RuleChainUpdateMsg constructRuleChainDeleteMsg(RuleChainId ruleChainId) { - return RuleChainUpdateMsg.newBuilder() - .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) - .setIdMSB(ruleChainId.getId().getMostSignificantBits()) - .setIdLSB(ruleChainId.getId().getLeastSignificantBits()).build(); - } - - @Override - public RuleChainMetadataUpdateMsg constructRuleChainMetadataUpdatedMsg(TenantId tenantId, - UpdateMsgType msgType, - RuleChainMetaData ruleChainMetaData, - EdgeVersion edgeVersion) { - RuleChainMetadataConstructor ruleChainMetadataConstructor = RuleChainMetadataConstructorFactory.getByEdgeVersion(edgeVersion); - return ruleChainMetadataConstructor.constructRuleChainMetadataUpdatedMsg(tenantId, msgType, ruleChainMetaData, edgeVersion); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructor.java deleted file mode 100644 index 43b103cb8a..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructor.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.rule; - -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.rule.RuleChainMetaData; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; -import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; - -public interface RuleChainMetadataConstructor { - - RuleChainMetadataUpdateMsg constructRuleChainMetadataUpdatedMsg(TenantId tenantId, - UpdateMsgType msgType, - RuleChainMetaData ruleChainMetaData, - EdgeVersion edgeVersion); -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorFactory.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorFactory.java deleted file mode 100644 index 22c36e47a6..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorFactory.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.rule; - -import org.thingsboard.server.gen.edge.v1.EdgeVersion; - -public final class RuleChainMetadataConstructorFactory { - - public static RuleChainMetadataConstructor getByEdgeVersion(EdgeVersion edgeVersion) { - switch (edgeVersion) { - case V_3_3_0: - return new RuleChainMetadataConstructorV330(); - case V_3_3_3: - case V_3_4_0: - case V_3_6_0: - case V_3_6_1: - return new RuleChainMetadataConstructorV340(); - case V_3_6_2: - default: - return new RuleChainMetadataConstructorV362(); - } - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorV330.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorV330.java deleted file mode 100644 index fe24a8267a..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorV330.java +++ /dev/null @@ -1,166 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.rule; - -import lombok.extern.slf4j.Slf4j; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.rule.engine.flow.TbRuleChainInputNode; -import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration; -import org.thingsboard.rule.engine.flow.TbRuleChainOutputNode; -import org.thingsboard.server.common.data.id.RuleChainId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.rule.NodeConnectionInfo; -import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo; -import org.thingsboard.server.common.data.rule.RuleChainMetaData; -import org.thingsboard.server.common.data.rule.RuleNode; -import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; - -import java.util.ArrayList; -import java.util.List; -import java.util.NavigableSet; -import java.util.TreeSet; -import java.util.UUID; -import java.util.stream.Collectors; - -@Slf4j -public class RuleChainMetadataConstructorV330 extends BaseRuleChainMetadataConstructor { - - private static final String RULE_CHAIN_INPUT_NODE = TbRuleChainInputNode.class.getName(); - private static final String TB_RULE_CHAIN_OUTPUT_NODE = TbRuleChainOutputNode.class.getName(); - - @Override - protected void constructRuleChainMetadataUpdatedMsg(TenantId tenantId, - RuleChainMetadataUpdateMsg.Builder builder, - RuleChainMetaData ruleChainMetaData) { - builder.setRuleChainIdMSB(ruleChainMetaData.getRuleChainId().getId().getMostSignificantBits()) - .setRuleChainIdLSB(ruleChainMetaData.getRuleChainId().getId().getLeastSignificantBits()); - List supportedNodes = filterNodes(ruleChainMetaData.getNodes()); - - NavigableSet removedNodeIndexes = getRemovedNodeIndexes(ruleChainMetaData.getNodes(), ruleChainMetaData.getConnections()); - List connections = filterConnections(ruleChainMetaData.getNodes(), ruleChainMetaData.getConnections(), removedNodeIndexes); - - List ruleChainConnections = new ArrayList<>(); - if (ruleChainMetaData.getRuleChainConnections() != null) { - ruleChainConnections.addAll(ruleChainMetaData.getRuleChainConnections()); - } - ruleChainConnections.addAll(addRuleChainConnections(ruleChainMetaData.getNodes(), ruleChainMetaData.getConnections())); - builder.addAllNodes(constructNodes(supportedNodes)) - .addAllConnections(constructConnections(connections)) - .addAllRuleChainConnections(constructRuleChainConnections(ruleChainConnections, removedNodeIndexes)); - if (ruleChainMetaData.getFirstNodeIndex() != null) { - Integer firstNodeIndex = ruleChainMetaData.getFirstNodeIndex(); - // decrease index because of removed nodes - for (Integer removedIndex : removedNodeIndexes) { - if (firstNodeIndex > removedIndex) { - firstNodeIndex = firstNodeIndex - 1; - } - } - builder.setFirstNodeIndex(firstNodeIndex); - } else { - builder.setFirstNodeIndex(-1); - } - } - - private NavigableSet getRemovedNodeIndexes(List nodes, List connections) { - TreeSet removedIndexes = new TreeSet<>(); - for (NodeConnectionInfo connection : connections) { - for (int i = 0; i < nodes.size(); i++) { - RuleNode node = nodes.get(i); - if (node.getType().equalsIgnoreCase(RULE_CHAIN_INPUT_NODE) - || node.getType().equalsIgnoreCase(TB_RULE_CHAIN_OUTPUT_NODE)) { - if (connection.getFromIndex() == i || connection.getToIndex() == i) { - removedIndexes.add(i); - } - } - } - } - return removedIndexes.descendingSet(); - } - - private List filterConnections(List nodes, - List connections, - NavigableSet removedNodeIndexes) { - List result = new ArrayList<>(); - if (connections != null) { - result = connections.stream().filter(conn -> { - for (int i = 0; i < nodes.size(); i++) { - RuleNode node = nodes.get(i); - if (node.getType().equalsIgnoreCase(RULE_CHAIN_INPUT_NODE) - || node.getType().equalsIgnoreCase(TB_RULE_CHAIN_OUTPUT_NODE)) { - if (conn.getFromIndex() == i || conn.getToIndex() == i) { - return false; - } - } - } - return true; - }).map(conn -> { - NodeConnectionInfo newConn = new NodeConnectionInfo(); - newConn.setFromIndex(conn.getFromIndex()); - newConn.setToIndex(conn.getToIndex()); - newConn.setType(conn.getType()); - return newConn; - }).collect(Collectors.toList()); - } - - // decrease index because of removed nodes - for (Integer removedIndex : removedNodeIndexes) { - for (NodeConnectionInfo newConn : result) { - if (newConn.getToIndex() > removedIndex) { - newConn.setToIndex(newConn.getToIndex() - 1); - } - if (newConn.getFromIndex() > removedIndex) { - newConn.setFromIndex(newConn.getFromIndex() - 1); - } - } - } - - return result; - } - - private List filterNodes(List nodes) { - List result = new ArrayList<>(); - for (RuleNode node : nodes) { - if (RULE_CHAIN_INPUT_NODE.equals(node.getType()) - || TB_RULE_CHAIN_OUTPUT_NODE.equals(node.getType())) { - log.trace("Skipping not supported rule node {}", node); - } else { - result.add(node); - } - } - return result; - } - - private List addRuleChainConnections(List nodes, List connections) { - List result = new ArrayList<>(); - for (int i = 0; i < nodes.size(); i++) { - RuleNode node = nodes.get(i); - if (node.getType().equalsIgnoreCase(RULE_CHAIN_INPUT_NODE)) { - for (NodeConnectionInfo connection : connections) { - if (connection.getToIndex() == i) { - RuleChainConnectionInfo e = new RuleChainConnectionInfo(); - e.setFromIndex(connection.getFromIndex()); - TbRuleChainInputNodeConfiguration configuration = JacksonUtil.treeToValue(node.getConfiguration(), TbRuleChainInputNodeConfiguration.class); - e.setTargetRuleChainId(new RuleChainId(UUID.fromString(configuration.getRuleChainId()))); - e.setAdditionalInfo(node.getAdditionalInfo()); - e.setType(connection.getType()); - result.add(e); - } - } - } - } - return result; - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorV340.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorV340.java deleted file mode 100644 index 087f3563b1..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorV340.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.rule; - -import lombok.extern.slf4j.Slf4j; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.rule.RuleChainMetaData; -import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; - -import java.util.TreeSet; - -@Slf4j -public class RuleChainMetadataConstructorV340 extends BaseRuleChainMetadataConstructor { - - @Override - protected void constructRuleChainMetadataUpdatedMsg(TenantId tenantId, - RuleChainMetadataUpdateMsg.Builder builder, - RuleChainMetaData ruleChainMetaData) { - builder.setRuleChainIdMSB(ruleChainMetaData.getRuleChainId().getId().getMostSignificantBits()) - .setRuleChainIdLSB(ruleChainMetaData.getRuleChainId().getId().getLeastSignificantBits()); - builder.addAllNodes(constructNodes(ruleChainMetaData.getNodes())) - .addAllConnections(constructConnections(ruleChainMetaData.getConnections())) - .addAllRuleChainConnections(constructRuleChainConnections(ruleChainMetaData.getRuleChainConnections(), new TreeSet<>())); - if (ruleChainMetaData.getFirstNodeIndex() != null) { - builder.setFirstNodeIndex(ruleChainMetaData.getFirstNodeIndex()); - } else { - builder.setFirstNodeIndex(-1); - } - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorV362.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorV362.java deleted file mode 100644 index a62a275672..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorV362.java +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.rule; - -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.rule.RuleChainMetaData; -import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; - -public class RuleChainMetadataConstructorV362 extends BaseRuleChainMetadataConstructor { - - @Override - protected void constructRuleChainMetadataUpdatedMsg(TenantId tenantId, RuleChainMetadataUpdateMsg.Builder builder, RuleChainMetaData ruleChainMetaData) { - builder.setEntity(JacksonUtil.toString(ruleChainMetaData)); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMsgConstructor.java deleted file mode 100644 index 89cf21a08c..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMsgConstructor.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.rule; - -import org.thingsboard.server.common.data.id.RuleChainId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.rule.RuleChain; -import org.thingsboard.server.common.data.rule.RuleChainMetaData; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; -import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; -import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.service.edge.rpc.constructor.MsgConstructor; - -public interface RuleChainMsgConstructor extends MsgConstructor { - - RuleChainUpdateMsg constructRuleChainUpdatedMsg(UpdateMsgType msgType, RuleChain ruleChain, boolean isRoot); - - RuleChainUpdateMsg constructRuleChainDeleteMsg(RuleChainId ruleChainId); - - RuleChainMetadataUpdateMsg constructRuleChainMetadataUpdatedMsg(TenantId tenantId, - UpdateMsgType msgType, - RuleChainMetaData ruleChainMetaData, - EdgeVersion edgeVersion); -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMsgConstructorFactory.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMsgConstructorFactory.java deleted file mode 100644 index cb7b116ea1..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMsgConstructorFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.rule; - -import org.springframework.stereotype.Component; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.constructor.BaseMsgConstructorFactory; - -@Component -@TbCoreComponent -public class RuleChainMsgConstructorFactory extends BaseMsgConstructorFactory { - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMsgConstructorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMsgConstructorV1.java deleted file mode 100644 index c4e8871187..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMsgConstructorV1.java +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.rule; - -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.rule.RuleChain; -import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Component -@TbCoreComponent -public class RuleChainMsgConstructorV1 extends BaseRuleChainMsgConstructor { - - @Override - public RuleChainUpdateMsg constructRuleChainUpdatedMsg(UpdateMsgType msgType, RuleChain ruleChain, boolean isRoot) { - RuleChainUpdateMsg.Builder builder = RuleChainUpdateMsg.newBuilder() - .setMsgType(msgType) - .setIdMSB(ruleChain.getId().getId().getMostSignificantBits()) - .setIdLSB(ruleChain.getId().getId().getLeastSignificantBits()) - .setName(ruleChain.getName()) - .setRoot(isRoot) - .setDebugMode(ruleChain.isDebugMode()) - .setConfiguration(JacksonUtil.toString(ruleChain.getConfiguration())); - if (ruleChain.getFirstRuleNodeId() != null) { - builder.setFirstRuleNodeIdMSB(ruleChain.getFirstRuleNodeId().getId().getMostSignificantBits()) - .setFirstRuleNodeIdLSB(ruleChain.getFirstRuleNodeId().getId().getLeastSignificantBits()); - } - return builder.build(); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMsgConstructorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMsgConstructorV2.java deleted file mode 100644 index fb03067ca0..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMsgConstructorV2.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.rule; - -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.rule.RuleChain; -import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Component -@TbCoreComponent -public class RuleChainMsgConstructorV2 extends BaseRuleChainMsgConstructor { - - @Override - public RuleChainUpdateMsg constructRuleChainUpdatedMsg(UpdateMsgType msgType, RuleChain ruleChain, boolean isRoot) { - boolean isTemplateRoot = ruleChain.isRoot(); - ruleChain.setRoot(isRoot); - RuleChainUpdateMsg result = RuleChainUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(ruleChain)) - .setIdMSB(ruleChain.getId().getId().getMostSignificantBits()) - .setIdLSB(ruleChain.getId().getId().getLeastSignificantBits()).build(); - ruleChain.setRoot(isTemplateRoot); - return result; - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/settings/AdminSettingsMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/settings/AdminSettingsMsgConstructor.java deleted file mode 100644 index a5fa5f3808..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/settings/AdminSettingsMsgConstructor.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.settings; - -import org.thingsboard.server.common.data.AdminSettings; -import org.thingsboard.server.gen.edge.v1.AdminSettingsUpdateMsg; -import org.thingsboard.server.service.edge.rpc.constructor.MsgConstructor; - -public interface AdminSettingsMsgConstructor extends MsgConstructor { - - AdminSettingsUpdateMsg constructAdminSettingsUpdateMsg(AdminSettings adminSettings); -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/settings/AdminSettingsMsgConstructorFactory.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/settings/AdminSettingsMsgConstructorFactory.java deleted file mode 100644 index 472e582b52..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/settings/AdminSettingsMsgConstructorFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.settings; - -import org.springframework.stereotype.Component; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.constructor.BaseMsgConstructorFactory; - -@Component -@TbCoreComponent -public class AdminSettingsMsgConstructorFactory extends BaseMsgConstructorFactory { - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/settings/AdminSettingsMsgConstructorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/settings/AdminSettingsMsgConstructorV1.java deleted file mode 100644 index 1f3942542d..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/settings/AdminSettingsMsgConstructorV1.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.settings; - -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.AdminSettings; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.gen.edge.v1.AdminSettingsUpdateMsg; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Component -@TbCoreComponent -public class AdminSettingsMsgConstructorV1 implements AdminSettingsMsgConstructor { - - @Override - public AdminSettingsUpdateMsg constructAdminSettingsUpdateMsg(AdminSettings adminSettings) { - AdminSettingsUpdateMsg.Builder builder = AdminSettingsUpdateMsg.newBuilder() - .setKey(adminSettings.getKey()) - .setJsonValue(JacksonUtil.toString(adminSettings.getJsonValue())); - if (TenantId.SYS_TENANT_ID.equals(adminSettings.getTenantId())) { - builder.setIsSystem(true); - } - return builder.build(); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/settings/AdminSettingsMsgConstructorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/settings/AdminSettingsMsgConstructorV2.java deleted file mode 100644 index d3cd44370b..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/settings/AdminSettingsMsgConstructorV2.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.settings; - -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.AdminSettings; -import org.thingsboard.server.gen.edge.v1.AdminSettingsUpdateMsg; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Component -@TbCoreComponent -public class AdminSettingsMsgConstructorV2 implements AdminSettingsMsgConstructor { - - @Override - public AdminSettingsUpdateMsg constructAdminSettingsUpdateMsg(AdminSettings adminSettings) { - return AdminSettingsUpdateMsg.newBuilder().setEntity(JacksonUtil.toString(adminSettings)).build(); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/telemetry/EntityDataMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/telemetry/EntityDataMsgConstructor.java deleted file mode 100644 index f7f3cc31dd..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/telemetry/EntityDataMsgConstructor.java +++ /dev/null @@ -1,116 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.telemetry; - -import com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; -import com.google.gson.reflect.TypeToken; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.adaptor.JsonConverter; -import org.thingsboard.server.common.data.DataConstants; -import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.gen.edge.v1.AttributeDeleteMsg; -import org.thingsboard.server.gen.edge.v1.EntityDataProto; -import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.util.TbCoreComponent; - -import java.util.List; - -@Slf4j -@Component -@TbCoreComponent -public class EntityDataMsgConstructor { - - public EntityDataProto constructEntityDataMsg(TenantId tenantId, EntityId entityId, EdgeEventActionType actionType, JsonElement entityData) { - EntityDataProto.Builder builder = EntityDataProto.newBuilder() - .setEntityIdMSB(entityId.getId().getMostSignificantBits()) - .setEntityIdLSB(entityId.getId().getLeastSignificantBits()) - .setEntityType(entityId.getEntityType().name()); - switch (actionType) { - case TIMESERIES_UPDATED: - try { - JsonObject data = entityData.getAsJsonObject(); - long ts; - if (data.get("ts") != null && !data.get("ts").isJsonNull()) { - ts = data.getAsJsonPrimitive("ts").getAsLong(); - } else { - ts = System.currentTimeMillis(); - } - builder.setPostTelemetryMsg(JsonConverter.convertToTelemetryProto(data.getAsJsonObject("data"), ts)); - } catch (Exception e) { - log.warn("[{}][{}] Can't convert to telemetry proto, entityData [{}]", tenantId, entityId, entityData, e); - } - break; - case ATTRIBUTES_UPDATED: - try { - JsonObject data = entityData.getAsJsonObject(); - TransportProtos.PostAttributeMsg attributesUpdatedMsg = JsonConverter.convertToAttributesProto(data.getAsJsonObject("kv")); - builder.setAttributesUpdatedMsg(attributesUpdatedMsg); - builder.setPostAttributeScope(getScopeOfDefault(data)); - if (data.get("ts") != null && !data.get("ts").isJsonNull()) { - builder.setAttributeTs(data.getAsJsonPrimitive("ts").getAsLong()); - } - } catch (Exception e) { - log.warn("[{}][{}] Can't convert to AttributesUpdatedMsg proto, entityData [{}]", tenantId, entityId, entityData, e); - } - break; - case POST_ATTRIBUTES: - try { - JsonObject data = entityData.getAsJsonObject(); - TransportProtos.PostAttributeMsg postAttributesMsg = JsonConverter.convertToAttributesProto(data.getAsJsonObject("kv")); - builder.setPostAttributesMsg(postAttributesMsg); - builder.setPostAttributeScope(getScopeOfDefault(data)); - if (data.get("ts") != null && !data.get("ts").isJsonNull()) { - builder.setAttributeTs(data.getAsJsonPrimitive("ts").getAsLong()); - } - } catch (Exception e) { - log.warn("[{}][{}] Can't convert to PostAttributesMsg, entityData [{}]", tenantId, entityId, entityData, e); - } - break; - case ATTRIBUTES_DELETED: - try { - AttributeDeleteMsg.Builder attributeDeleteMsg = AttributeDeleteMsg.newBuilder(); - attributeDeleteMsg.setScope(entityData.getAsJsonObject().getAsJsonPrimitive("scope").getAsString()); - JsonArray jsonArray = entityData.getAsJsonObject().getAsJsonArray("keys"); - List keys = new Gson().fromJson(jsonArray.toString(), new TypeToken<>(){}.getType()); - attributeDeleteMsg.addAllAttributeNames(keys); - attributeDeleteMsg.build(); - builder.setAttributeDeleteMsg(attributeDeleteMsg); - } catch (Exception e) { - log.warn("[{}][{}] Can't convert to AttributeDeleteMsg proto, entityData [{}]", tenantId, entityId, entityData, e); - } - break; - } - return builder.build(); - } - - private String getScopeOfDefault(JsonObject data) { - JsonPrimitive scope = data.getAsJsonPrimitive("scope"); - String result = DataConstants.SERVER_SCOPE; - if (scope != null && StringUtils.isNotBlank(scope.getAsString())) { - result = scope.getAsString(); - } - return result; - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructor.java deleted file mode 100644 index a1299b3f97..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructor.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.tenant; - -import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; -import org.thingsboard.server.gen.edge.v1.TenantProfileUpdateMsg; -import org.thingsboard.server.gen.edge.v1.TenantUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.service.edge.rpc.constructor.MsgConstructor; - -public interface TenantMsgConstructor extends MsgConstructor { - - TenantUpdateMsg constructTenantUpdateMsg(UpdateMsgType msgType, Tenant tenant); - - TenantProfileUpdateMsg constructTenantProfileUpdateMsg(UpdateMsgType msgType, TenantProfile tenantProfile, EdgeVersion edgeVersion); -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorFactory.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorFactory.java deleted file mode 100644 index 09441d8e91..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.tenant; - -import org.springframework.stereotype.Component; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.constructor.BaseMsgConstructorFactory; - -@Component -@TbCoreComponent -public class TenantMsgConstructorFactory extends BaseMsgConstructorFactory { - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorV1.java deleted file mode 100644 index a9accf572d..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorV1.java +++ /dev/null @@ -1,111 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.tenant; - -import com.google.protobuf.ByteString; -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; -import org.thingsboard.server.gen.edge.v1.TenantProfileUpdateMsg; -import org.thingsboard.server.gen.edge.v1.TenantUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.utils.EdgeVersionUtils; - -@Component -@TbCoreComponent -public class TenantMsgConstructorV1 implements TenantMsgConstructor { - - @Override - public TenantUpdateMsg constructTenantUpdateMsg(UpdateMsgType msgType, Tenant tenant) { - TenantUpdateMsg.Builder builder = TenantUpdateMsg.newBuilder() - .setMsgType(msgType) - .setIdMSB(tenant.getId().getId().getMostSignificantBits()) - .setIdLSB(tenant.getId().getId().getLeastSignificantBits()) - .setTitle(tenant.getTitle()) - .setProfileIdMSB(tenant.getTenantProfileId().getId().getMostSignificantBits()) - .setProfileIdLSB(tenant.getTenantProfileId().getId().getLeastSignificantBits()) - .setRegion(tenant.getRegion()); - if (tenant.getCountry() != null) { - builder.setCountry(tenant.getCountry()); - } - if (tenant.getState() != null) { - builder.setState(tenant.getState()); - } - if (tenant.getCity() != null) { - builder.setCity(tenant.getCity()); - } - if (tenant.getAddress() != null) { - builder.setAddress(tenant.getAddress()); - } - if (tenant.getAddress2() != null) { - builder.setAddress2(tenant.getAddress2()); - } - if (tenant.getZip() != null) { - builder.setZip(tenant.getZip()); - } - if (tenant.getPhone() != null) { - builder.setPhone(tenant.getPhone()); - } - if (tenant.getEmail() != null) { - builder.setEmail(tenant.getEmail()); - } - if (tenant.getAdditionalInfo() != null) { - builder.setAdditionalInfo(JacksonUtil.toString(tenant.getAdditionalInfo())); - } - return builder.build(); - } - - @Override - public TenantProfileUpdateMsg constructTenantProfileUpdateMsg(UpdateMsgType msgType, TenantProfile tenantProfile, EdgeVersion edgeVersion) { - tenantProfile = JacksonUtil.clone(tenantProfile); - // clear all config - var tenantProfileData = tenantProfile.getProfileData(); - var configuration = tenantProfile.getDefaultProfileConfiguration(); - configuration.setRpcTtlDays(0); - configuration.setMaxJSExecutions(0); - configuration.setMaxREExecutions(0); - configuration.setMaxDPStorageDays(0); - configuration.setMaxTbelExecutions(0); - configuration.setQueueStatsTtlDays(0); - configuration.setMaxTransportMessages(0); - configuration.setDefaultStorageTtlDays(0); - configuration.setMaxTransportDataPoints(0); - configuration.setRuleEngineExceptionsTtlDays(0); - configuration.setMaxRuleNodeExecutionsPerMessage(0); - configuration.setMaxDebugModeDurationMinutes(0); - tenantProfileData.setConfiguration(configuration); - tenantProfile.setProfileData(tenantProfileData); - - ByteString profileData = EdgeVersionUtils.isEdgeVersionOlderThan(edgeVersion, EdgeVersion.V_3_6_2) ? - ByteString.empty() : ByteString.copyFrom(tenantProfile.getProfileDataBytes()); - TenantProfileUpdateMsg.Builder builder = TenantProfileUpdateMsg.newBuilder() - .setMsgType(msgType) - .setIdMSB(tenantProfile.getId().getId().getMostSignificantBits()) - .setIdLSB(tenantProfile.getId().getId().getLeastSignificantBits()) - .setName(tenantProfile.getName()) - .setDefault(tenantProfile.isDefault()) - .setIsolatedRuleChain(tenantProfile.isIsolatedTbRuleEngine()) - .setProfileDataBytes(profileData); - if (tenantProfile.getDescription() != null) { - builder.setDescription(tenantProfile.getDescription()); - } - return builder.build(); - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorV2.java deleted file mode 100644 index ee0c6a0d72..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorV2.java +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.tenant; - -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; -import org.thingsboard.server.gen.edge.v1.TenantProfileUpdateMsg; -import org.thingsboard.server.gen.edge.v1.TenantUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Component -@TbCoreComponent -public class TenantMsgConstructorV2 implements TenantMsgConstructor { - - @Override - public TenantUpdateMsg constructTenantUpdateMsg(UpdateMsgType msgType, Tenant tenant) { - return TenantUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(tenant)).build(); - } - - @Override - public TenantProfileUpdateMsg constructTenantProfileUpdateMsg(UpdateMsgType msgType, TenantProfile tenantProfile, EdgeVersion edgeVersion) { - return TenantProfileUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(tenantProfile)).build(); - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/user/BaseUserMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/user/BaseUserMsgConstructor.java deleted file mode 100644 index ab2e2dec14..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/user/BaseUserMsgConstructor.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.user; - -import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.gen.edge.v1.UserUpdateMsg; - -public abstract class BaseUserMsgConstructor implements UserMsgConstructor { - - @Override - public UserUpdateMsg constructUserDeleteMsg(UserId userId) { - return UserUpdateMsg.newBuilder() - .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) - .setIdMSB(userId.getId().getMostSignificantBits()) - .setIdLSB(userId.getId().getLeastSignificantBits()).build(); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/user/UserMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/user/UserMsgConstructor.java deleted file mode 100644 index f0fb9bd8a5..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/user/UserMsgConstructor.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.user; - -import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.security.UserCredentials; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.gen.edge.v1.UserCredentialsUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UserUpdateMsg; -import org.thingsboard.server.service.edge.rpc.constructor.MsgConstructor; - -public interface UserMsgConstructor extends MsgConstructor { - - UserUpdateMsg constructUserUpdatedMsg(UpdateMsgType msgType, User user); - - UserUpdateMsg constructUserDeleteMsg(UserId userId); - - UserCredentialsUpdateMsg constructUserCredentialsUpdatedMsg(UserCredentials userCredentials); -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/user/UserMsgConstructorFactory.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/user/UserMsgConstructorFactory.java deleted file mode 100644 index f2e42a1dcc..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/user/UserMsgConstructorFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.user; - -import org.springframework.stereotype.Component; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.constructor.BaseMsgConstructorFactory; - -@Component -@TbCoreComponent -public class UserMsgConstructorFactory extends BaseMsgConstructorFactory { - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/user/UserMsgConstructorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/user/UserMsgConstructorV1.java deleted file mode 100644 index 309de42b1e..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/user/UserMsgConstructorV1.java +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.user; - -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.security.UserCredentials; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.gen.edge.v1.UserCredentialsUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UserUpdateMsg; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Component -@TbCoreComponent -public class UserMsgConstructorV1 extends BaseUserMsgConstructor { - - @Override - public UserUpdateMsg constructUserUpdatedMsg(UpdateMsgType msgType, User user) { - UserUpdateMsg.Builder builder = UserUpdateMsg.newBuilder() - .setMsgType(msgType) - .setIdMSB(user.getId().getId().getMostSignificantBits()) - .setIdLSB(user.getId().getId().getLeastSignificantBits()) - .setEmail(user.getEmail()) - .setAuthority(user.getAuthority().name()); - if (user.getCustomerId() != null) { - builder.setCustomerIdMSB(user.getCustomerId().getId().getMostSignificantBits()); - builder.setCustomerIdLSB(user.getCustomerId().getId().getLeastSignificantBits()); - } - if (user.getFirstName() != null) { - builder.setFirstName(user.getFirstName()); - } - if (user.getLastName() != null) { - builder.setLastName(user.getLastName()); - } - if (user.getAdditionalInfo() != null) { - builder.setAdditionalInfo(JacksonUtil.toString(user.getAdditionalInfo())); - } - return builder.build(); - } - - @Override - public UserCredentialsUpdateMsg constructUserCredentialsUpdatedMsg(UserCredentials userCredentials) { - return UserCredentialsUpdateMsg.newBuilder() - .setUserIdMSB(userCredentials.getUserId().getId().getMostSignificantBits()) - .setUserIdLSB(userCredentials.getUserId().getId().getLeastSignificantBits()) - .setEnabled(userCredentials.isEnabled()) - .setPassword(userCredentials.getPassword()).build(); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/user/UserMsgConstructorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/user/UserMsgConstructorV2.java deleted file mode 100644 index e26e085a3b..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/user/UserMsgConstructorV2.java +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.user; - -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.security.UserCredentials; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.gen.edge.v1.UserCredentialsUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UserUpdateMsg; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Component -@TbCoreComponent -public class UserMsgConstructorV2 extends BaseUserMsgConstructor { - - @Override - public UserUpdateMsg constructUserUpdatedMsg(UpdateMsgType msgType, User user) { - return UserUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(user)) - .setIdMSB(user.getId().getId().getMostSignificantBits()) - .setIdLSB(user.getId().getId().getLeastSignificantBits()).build(); - } - - @Override - public UserCredentialsUpdateMsg constructUserCredentialsUpdatedMsg(UserCredentials userCredentials) { - return UserCredentialsUpdateMsg.newBuilder().setEntity(JacksonUtil.toString(userCredentials)).build(); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/widget/BaseWidgetMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/widget/BaseWidgetMsgConstructor.java deleted file mode 100644 index 5ccf0cf85f..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/widget/BaseWidgetMsgConstructor.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.widget; - -import org.thingsboard.server.common.data.id.WidgetTypeId; -import org.thingsboard.server.common.data.id.WidgetsBundleId; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg; -import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg; - -public abstract class BaseWidgetMsgConstructor implements WidgetMsgConstructor { - - @Override - public WidgetsBundleUpdateMsg constructWidgetsBundleDeleteMsg(WidgetsBundleId widgetsBundleId) { - return WidgetsBundleUpdateMsg.newBuilder() - .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) - .setIdMSB(widgetsBundleId.getId().getMostSignificantBits()) - .setIdLSB(widgetsBundleId.getId().getLeastSignificantBits()) - .build(); - } - - @Override - public WidgetTypeUpdateMsg constructWidgetTypeDeleteMsg(WidgetTypeId widgetTypeId) { - return WidgetTypeUpdateMsg.newBuilder() - .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) - .setIdMSB(widgetTypeId.getId().getMostSignificantBits()) - .setIdLSB(widgetTypeId.getId().getLeastSignificantBits()) - .build(); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/widget/WidgetMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/widget/WidgetMsgConstructor.java deleted file mode 100644 index b5f78026bd..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/widget/WidgetMsgConstructor.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.widget; - -import org.thingsboard.server.common.data.id.WidgetTypeId; -import org.thingsboard.server.common.data.id.WidgetsBundleId; -import org.thingsboard.server.common.data.widget.WidgetTypeDetails; -import org.thingsboard.server.common.data.widget.WidgetsBundle; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg; -import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg; -import org.thingsboard.server.service.edge.rpc.constructor.MsgConstructor; - -import java.util.List; - -public interface WidgetMsgConstructor extends MsgConstructor { - - WidgetsBundleUpdateMsg constructWidgetsBundleUpdateMsg(UpdateMsgType msgType, WidgetsBundle widgetsBundle, List widgets); - - WidgetsBundleUpdateMsg constructWidgetsBundleDeleteMsg(WidgetsBundleId widgetsBundleId); - - WidgetTypeUpdateMsg constructWidgetTypeUpdateMsg(UpdateMsgType msgType, WidgetTypeDetails widgetTypeDetails, EdgeVersion edgeVersion); - - WidgetTypeUpdateMsg constructWidgetTypeDeleteMsg(WidgetTypeId widgetTypeId); -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/widget/WidgetMsgConstructorFactory.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/widget/WidgetMsgConstructorFactory.java deleted file mode 100644 index 421523c3d8..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/widget/WidgetMsgConstructorFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.widget; - -import org.springframework.stereotype.Component; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.constructor.BaseMsgConstructorFactory; - -@Component -@TbCoreComponent -public class WidgetMsgConstructorFactory extends BaseMsgConstructorFactory { - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/widget/WidgetMsgConstructorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/widget/WidgetMsgConstructorV1.java deleted file mode 100644 index 0201f6276b..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/widget/WidgetMsgConstructorV1.java +++ /dev/null @@ -1,115 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.widget; - -import com.google.protobuf.ByteString; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.widget.WidgetTypeDetails; -import org.thingsboard.server.common.data.widget.WidgetsBundle; -import org.thingsboard.server.dao.resource.ImageService; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg; -import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.utils.EdgeVersionUtils; - -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.List; - -@Component -@TbCoreComponent -public class WidgetMsgConstructorV1 extends BaseWidgetMsgConstructor { - - @Autowired - private ImageService imageService; - - @Override - public WidgetsBundleUpdateMsg constructWidgetsBundleUpdateMsg(UpdateMsgType msgType, WidgetsBundle widgetsBundle, List widgets) { - widgetsBundle = JacksonUtil.clone(widgetsBundle); - imageService.inlineImageForEdge(widgetsBundle); - WidgetsBundleUpdateMsg.Builder builder = WidgetsBundleUpdateMsg.newBuilder() - .setMsgType(msgType) - .setIdMSB(widgetsBundle.getId().getId().getMostSignificantBits()) - .setIdLSB(widgetsBundle.getId().getId().getLeastSignificantBits()) - .setTitle(widgetsBundle.getTitle()) - .setAlias(widgetsBundle.getAlias()); - if (widgetsBundle.getImage() != null) { - builder.setImage(ByteString.copyFrom(widgetsBundle.getImage().getBytes(StandardCharsets.UTF_8))); - } - if (widgetsBundle.getDescription() != null) { - builder.setDescription(widgetsBundle.getDescription()); - } - if (widgetsBundle.getOrder() != null) { - builder.setOrder(widgetsBundle.getOrder()); - } - if (widgetsBundle.getTenantId().equals(TenantId.SYS_TENANT_ID)) { - builder.setIsSystem(true); - } - builder.setWidgets(JacksonUtil.toString(widgets)); - return builder.build(); - } - - @Override - public WidgetTypeUpdateMsg constructWidgetTypeUpdateMsg(UpdateMsgType msgType, WidgetTypeDetails widgetTypeDetails, EdgeVersion edgeVersion) { - widgetTypeDetails = JacksonUtil.clone(widgetTypeDetails); - imageService.inlineImagesForEdge(widgetTypeDetails); - WidgetTypeUpdateMsg.Builder builder = WidgetTypeUpdateMsg.newBuilder() - .setMsgType(msgType) - .setIdMSB(widgetTypeDetails.getId().getId().getMostSignificantBits()) - .setIdLSB(widgetTypeDetails.getId().getId().getLeastSignificantBits()); - if (widgetTypeDetails.getFqn() != null) { - builder.setFqn(widgetTypeDetails.getFqn()); - if (widgetTypeDetails.getFqn().contains(".")) { - String[] aliases = widgetTypeDetails.getFqn().split("\\.", 2); - if (aliases.length == 2) { - builder.setBundleAlias(aliases[0]); - builder.setAlias(aliases[1]); - } - } - } - if (widgetTypeDetails.getName() != null) { - builder.setName(widgetTypeDetails.getName()); - } - if (widgetTypeDetails.getDescriptor() != null) { - builder.setDescriptorJson(JacksonUtil.toString(widgetTypeDetails.getDescriptor())); - } - if (widgetTypeDetails.getTenantId().equals(TenantId.SYS_TENANT_ID)) { - builder.setIsSystem(true); - } - if (widgetTypeDetails.getImage() != null) { - builder.setImage(widgetTypeDetails.getImage()); - } - if (widgetTypeDetails.getDescription() != null) { - if (EdgeVersionUtils.isEdgeVersionOlderThan(edgeVersion, EdgeVersion.V_3_6_0) && - widgetTypeDetails.getDescription().length() > 255) { - builder.setDescription(widgetTypeDetails.getDescription().substring(0, 254)); - } else { - builder.setDescription(widgetTypeDetails.getDescription()); - } - } - builder.setDeprecated(widgetTypeDetails.isDeprecated()); - if (widgetTypeDetails.getTags() != null) { - builder.addAllTags(Arrays.asList(widgetTypeDetails.getTags())); - } - return builder.build(); - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/widget/WidgetMsgConstructorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/widget/WidgetMsgConstructorV2.java deleted file mode 100644 index 1b1d7e246e..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/widget/WidgetMsgConstructorV2.java +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.constructor.widget; - -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.widget.WidgetTypeDetails; -import org.thingsboard.server.common.data.widget.WidgetsBundle; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg; -import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg; -import org.thingsboard.server.queue.util.TbCoreComponent; - -import java.util.List; - -@Component -@TbCoreComponent -public class WidgetMsgConstructorV2 extends BaseWidgetMsgConstructor { - - @Override - public WidgetsBundleUpdateMsg constructWidgetsBundleUpdateMsg(UpdateMsgType msgType, WidgetsBundle widgetsBundle, List widgets) { - return WidgetsBundleUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(widgetsBundle)) - .setWidgets(JacksonUtil.toString(widgets)) - .setIdMSB(widgetsBundle.getId().getId().getMostSignificantBits()) - .setIdLSB(widgetsBundle.getId().getId().getLeastSignificantBits()).build(); - } - - @Override - public WidgetTypeUpdateMsg constructWidgetTypeUpdateMsg(UpdateMsgType msgType, WidgetTypeDetails widgetTypeDetails, EdgeVersion edgeVersion) { - return WidgetTypeUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(widgetTypeDetails)) - .setIdMSB(widgetTypeDetails.getId().getId().getMostSignificantBits()) - .setIdLSB(widgetTypeDetails.getId().getId().getLeastSignificantBits()).build(); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/GeneralEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/GeneralEdgeEventFetcher.java index b2a1044981..93461f8c39 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/GeneralEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/GeneralEdgeEventFetcher.java @@ -54,23 +54,25 @@ public class GeneralEdgeEventFetcher implements EdgeEventFetcher { log.trace("[{}] Finding general edge events [{}], seqIdStart = {}, pageLink = {}", tenantId, edge.getId(), seqIdStart, pageLink); PageData edgeEvents = edgeEventService.findEdgeEvents(tenantId, edge.getId(), seqIdStart, null, (TimePageLink) pageLink); - if (edgeEvents.getData().isEmpty()) { + if (!edgeEvents.getData().isEmpty()) { + return edgeEvents; + } + if (seqIdStart > this.maxReadRecordsCount) { edgeEvents = edgeEventService.findEdgeEvents(tenantId, edge.getId(), 0L, Math.max(this.maxReadRecordsCount, seqIdStart - this.maxReadRecordsCount), (TimePageLink) pageLink); if (edgeEvents.getData().stream().anyMatch(ee -> ee.getSeqId() < seqIdStart)) { log.info("[{}] seqId column of edge_event table started new cycle [{}]", tenantId, edge.getId()); this.seqIdNewCycleStarted = true; this.seqIdStart = 0L; - } else { - edgeEvents = new PageData<>(); - log.warn("[{}] unexpected edge notification message received. " + - "no new events found and seqId column of edge_event table doesn't started new cycle [{}]", tenantId, edge.getId()); + return edgeEvents; } } - return edgeEvents; + log.info("[{}] Unexpected edge notification message received. " + + "No new events found, and the seqId column of the edge_event table has not started a new cycle [{}].", tenantId, edge.getId()); + return new PageData<>(); } catch (Exception e) { - log.error("[{}] failed to find edge events [{}]", tenantId, edge.getId()); + log.error("[{}] Failed to find edge events [{}]", tenantId, edge.getId(), e); + return new PageData<>(); } - return new PageData<>(); } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java index eaf3c1a95a..b0aaed3b05 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java @@ -20,6 +20,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.EdgeUtils; @@ -70,11 +71,12 @@ import java.util.concurrent.locks.ReentrantLock; import static org.thingsboard.server.dao.edge.BaseRelatedEdgesService.RELATED_EDGES_CACHE_ITEMS; @Slf4j -public abstract class BaseEdgeProcessor { +public abstract class BaseEdgeProcessor implements EdgeProcessor { protected static final Lock deviceCreationLock = new ReentrantLock(); protected static final Lock assetCreationLock = new ReentrantLock(); + @Lazy @Autowired protected EdgeContextComponent edgeCtx; @@ -120,7 +122,7 @@ public abstract class BaseEdgeProcessor { private boolean doSaveIfEdgeIsOffline(EdgeEventType type, EdgeEventActionType action) { return switch (action) { case TIMESERIES_UPDATED, ALARM_ACK, ALARM_CLEAR, ALARM_ASSIGNED, ALARM_UNASSIGNED, ADDED_COMMENT, - UPDATED_COMMENT -> true; + UPDATED_COMMENT, DELETED -> true; default -> switch (type) { case ALARM, ALARM_COMMENT, RULE_CHAIN, RULE_CHAIN_METADATA, USER, CUSTOMER, TENANT, TENANT_PROFILE, WIDGETS_BUNDLE, WIDGET_TYPE, ADMIN_SETTINGS, OTA_PACKAGE, QUEUE, RELATION, NOTIFICATION_TEMPLATE, NOTIFICATION_TARGET, @@ -187,6 +189,7 @@ public abstract class BaseEdgeProcessor { }; } + @Override public ListenableFuture processEntityNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType()); EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorFactory.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorFactory.java deleted file mode 100644 index a8ca885c7b..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorFactory.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.processor; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Component -@TbCoreComponent -public abstract class BaseEdgeProcessorFactory { - - @Autowired - protected T v1Processor; - - @Autowired - protected U v2Processor; - - public EdgeProcessor getProcessorByEdgeVersion(EdgeVersion edgeVersion) { - return switch (edgeVersion) { - case V_3_3_0, V_3_3_3, V_3_4_0, V_3_6_0, V_3_6_1 -> v1Processor; - default -> v2Processor; - }; - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EdgeProcessor.java index dc2ea115ee..98342998a0 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EdgeProcessor.java @@ -15,5 +15,23 @@ */ package org.thingsboard.server.service.edge.rpc.processor; +import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventType; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.gen.edge.v1.DownlinkMsg; +import org.thingsboard.server.gen.transport.TransportProtos; + public interface EdgeProcessor { + + ListenableFuture processEntityNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto msg); + + default DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { + return null; + } + + default EdgeEventType getEdgeEventType() { + return null; + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java index f0a8012660..72ff03128a 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java @@ -15,47 +15,33 @@ */ package org.thingsboard.server.service.edge.rpc.processor.alarm; -import com.fasterxml.jackson.databind.JsonNode; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.alarm.Alarm; -import org.thingsboard.server.common.data.alarm.AlarmComment; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.EdgeId; -import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.PageDataIterableByTenantIdEntityId; -import org.thingsboard.server.dao.entity.EntityService; -import org.thingsboard.server.gen.edge.v1.AlarmCommentUpdateMsg; import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.service.edge.rpc.constructor.alarm.AlarmMsgConstructor; -import org.thingsboard.server.service.edge.rpc.constructor.alarm.AlarmMsgConstructorFactory; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; -import java.util.ArrayList; import java.util.List; import java.util.UUID; -import static org.thingsboard.server.dao.edge.BaseRelatedEdgesService.RELATED_EDGES_CACHE_ITEMS; - @Slf4j -public abstract class AlarmEdgeProcessor extends BaseAlarmProcessor implements AlarmProcessor { - - @Autowired - private AlarmMsgConstructorFactory alarmMsgConstructorFactory; - - @Autowired - private EntityService entityService; +@Component +@TbCoreComponent +public class AlarmEdgeProcessor extends BaseAlarmProcessor implements AlarmProcessor { @Override public ListenableFuture processAlarmMsgFromEdge(TenantId tenantId, EdgeId edgeId, AlarmUpdateMsg alarmUpdateMsg) { @@ -69,9 +55,8 @@ public abstract class AlarmEdgeProcessor extends BaseAlarmProcessor implements A } @Override - public DownlinkMsg convertAlarmEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { - AlarmUpdateMsg alarmUpdateMsg = convertAlarmEventToAlarmMsg(edgeEvent.getTenantId(), edgeEvent.getEntityId(), - edgeEvent.getAction(), edgeEvent.getBody(), edgeVersion); + public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { + AlarmUpdateMsg alarmUpdateMsg = convertAlarmEventToAlarmMsg(edgeEvent); if (alarmUpdateMsg != null) { return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) @@ -82,37 +67,7 @@ public abstract class AlarmEdgeProcessor extends BaseAlarmProcessor implements A } @Override - public ListenableFuture processAlarmCommentMsgFromEdge(TenantId tenantId, EdgeId edgeId, AlarmCommentUpdateMsg alarmCommentUpdateMsg) { - log.trace("[{}] processAlarmCommentMsgFromEdge [{}]", tenantId, alarmCommentUpdateMsg); - try { - edgeSynchronizationManager.getEdgeId().set(edgeId); - return processAlarmCommentMsg(tenantId, alarmCommentUpdateMsg); - } finally { - edgeSynchronizationManager.getEdgeId().remove(); - } - } - - @Override - public DownlinkMsg convertAlarmCommentEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { - UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - var msgConstructor = (AlarmMsgConstructor) alarmMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion); - switch (edgeEvent.getAction()) { - case ADDED_COMMENT: - case UPDATED_COMMENT: - case DELETED_COMMENT: - AlarmComment alarmComment = JacksonUtil.convertValue(edgeEvent.getBody(), AlarmComment.class); - if (alarmComment != null) { - return DownlinkMsg.newBuilder() - .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addAlarmCommentUpdateMsg(msgConstructor.constructAlarmCommentUpdatedMsg(msgType, alarmComment)) - .build(); - } - default: - return null; - } - } - - public ListenableFuture processAlarmNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { + public ListenableFuture processEntityNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); AlarmId alarmId = new AlarmId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); EdgeId originatorEdgeId = safeGetEdgeId(edgeNotificationMsg.getOriginatorEdgeIdMSB(), edgeNotificationMsg.getOriginatorEdgeIdLSB()); @@ -140,55 +95,29 @@ public abstract class AlarmEdgeProcessor extends BaseAlarmProcessor implements A }, dbCallbackExecutorService); } - public ListenableFuture processAlarmCommentNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { - EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); - AlarmId alarmId = new AlarmId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); - EdgeId originatorEdgeId = safeGetEdgeId(edgeNotificationMsg.getOriginatorEdgeIdMSB(), edgeNotificationMsg.getOriginatorEdgeIdLSB()); - AlarmComment alarmComment = JacksonUtil.fromString(edgeNotificationMsg.getBody(), AlarmComment.class); - if (alarmComment == null) { - return Futures.immediateFuture(null); - } - Alarm alarmById = edgeCtx.getAlarmService().findAlarmById(tenantId, new AlarmId(alarmComment.getAlarmId().getId())); - List> delFutures = pushEventToAllRelatedEdges(tenantId, alarmById.getOriginator(), - alarmId, actionType, JacksonUtil.valueToTree(alarmComment), originatorEdgeId, EdgeEventType.ALARM_COMMENT); - return Futures.transform(Futures.allAsList(delFutures), voids -> null, dbCallbackExecutorService); - } - - private List> pushEventToAllRelatedEdges(TenantId tenantId, EntityId originatorId, AlarmId alarmId, - EdgeEventActionType actionType, JsonNode body, EdgeId sourceEdgeId, - EdgeEventType edgeEventType) { - List> futures = new ArrayList<>(); - PageDataIterableByTenantIdEntityId edgeIds = - new PageDataIterableByTenantIdEntityId<>(edgeCtx.getEdgeService()::findRelatedEdgeIdsByEntityId, tenantId, originatorId, RELATED_EDGES_CACHE_ITEMS); - for (EdgeId relatedEdgeId : edgeIds) { - if (!relatedEdgeId.equals(sourceEdgeId)) { - futures.add(saveEdgeEvent(tenantId, relatedEdgeId, edgeEventType, actionType, alarmId, body)); - } - } - return futures; - } - - private AlarmUpdateMsg convertAlarmEventToAlarmMsg(TenantId tenantId, UUID entityId, EdgeEventActionType actionType, JsonNode body, EdgeVersion edgeVersion) { - AlarmId alarmId = new AlarmId(entityId); - UpdateMsgType msgType = getUpdateMsgType(actionType); - var msgConstructor = (AlarmMsgConstructor) alarmMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion); - switch (actionType) { + private AlarmUpdateMsg convertAlarmEventToAlarmMsg(EdgeEvent edgeEvent) { + AlarmId alarmId = new AlarmId(edgeEvent.getEntityId()); + UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); + switch (edgeEvent.getAction()) { case ADDED, UPDATED, ALARM_ACK, ALARM_CLEAR -> { - Alarm alarm = edgeCtx.getAlarmService().findAlarmById(tenantId, alarmId); + Alarm alarm = edgeCtx.getAlarmService().findAlarmById(edgeEvent.getTenantId(), alarmId); if (alarm != null) { - return msgConstructor.constructAlarmUpdatedMsg(msgType, alarm, - entityService.fetchEntityName(tenantId, alarm.getOriginator()).orElse(null)); + return EdgeMsgConstructorUtils.constructAlarmUpdatedMsg(msgType, alarm); } } case ALARM_DELETE, DELETED -> { - Alarm deletedAlarm = JacksonUtil.convertValue(body, Alarm.class); + Alarm deletedAlarm = JacksonUtil.convertValue(edgeEvent.getBody(), Alarm.class); if (deletedAlarm != null) { - return msgConstructor.constructAlarmUpdatedMsg(msgType, deletedAlarm, - entityService.fetchEntityName(tenantId, deletedAlarm.getOriginator()).orElse(null)); + return EdgeMsgConstructorUtils.constructAlarmUpdatedMsg(msgType, deletedAlarm); } } } return null; } + @Override + public EdgeEventType getEdgeEventType() { + return EdgeEventType.ALARM; + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessorV1.java deleted file mode 100644 index 18e65e6bac..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessorV1.java +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.processor.alarm; - -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.alarm.Alarm; -import org.thingsboard.server.common.data.alarm.AlarmSeverity; -import org.thingsboard.server.common.data.alarm.AlarmStatus; -import org.thingsboard.server.common.data.id.AlarmId; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Component -@TbCoreComponent -public class AlarmEdgeProcessorV1 extends AlarmEdgeProcessor { - - @Override - protected Alarm constructAlarmFromUpdateMsg(TenantId tenantId, AlarmId alarmId, EntityId originatorId, AlarmUpdateMsg alarmUpdateMsg) { - Alarm alarm = new Alarm(); - alarm.setId(alarmId); - alarm.setTenantId(tenantId); - alarm.setOriginator(originatorId); - alarm.setType(alarmUpdateMsg.getName()); - alarm.setSeverity(AlarmSeverity.valueOf(alarmUpdateMsg.getSeverity())); - alarm.setStartTs(alarmUpdateMsg.getStartTs()); - AlarmStatus alarmStatus = AlarmStatus.valueOf(alarmUpdateMsg.getStatus()); - alarm.setClearTs(alarmUpdateMsg.getClearTs()); - alarm.setPropagate(alarmUpdateMsg.getPropagate()); - alarm.setCleared(alarmStatus.isCleared()); - alarm.setAcknowledged(alarmStatus.isAck()); - alarm.setAckTs(alarmUpdateMsg.getAckTs()); - alarm.setEndTs(alarmUpdateMsg.getEndTs()); - alarm.setDetails(JacksonUtil.toJsonNode(alarmUpdateMsg.getDetails())); - return alarm; - } - - @Override - protected EntityId getAlarmOriginatorFromMsg(TenantId tenantId, AlarmUpdateMsg alarmUpdateMsg) { - return getAlarmOriginator(tenantId, alarmUpdateMsg.getOriginatorName(), - EntityType.valueOf(alarmUpdateMsg.getOriginatorType())); - } - - private EntityId getAlarmOriginator(TenantId tenantId, String entityName, EntityType entityType) { - return switch (entityType) { - case DEVICE -> edgeCtx.getDeviceService().findDeviceByTenantIdAndName(tenantId, entityName).getId(); - case ASSET -> edgeCtx.getAssetService().findAssetByTenantIdAndName(tenantId, entityName).getId(); - case ENTITY_VIEW -> edgeCtx.getEntityViewService().findEntityViewByTenantIdAndName(tenantId, entityName).getId(); - default -> null; - }; - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessorV2.java deleted file mode 100644 index dd49af252d..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessorV2.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.processor.alarm; - -import org.springframework.context.annotation.Primary; -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.alarm.Alarm; -import org.thingsboard.server.common.data.id.AlarmId; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Primary -@Component -@TbCoreComponent -public class AlarmEdgeProcessorV2 extends AlarmEdgeProcessor { - - @Override - protected EntityId getAlarmOriginatorFromMsg(TenantId tenantId, AlarmUpdateMsg alarmUpdateMsg) { - Alarm alarm = JacksonUtil.fromString(alarmUpdateMsg.getEntity(), Alarm.class, true); - return alarm != null ? alarm.getOriginator() : null; - } - - @Override - protected Alarm constructAlarmFromUpdateMsg(TenantId tenantId, AlarmId alarmId, EntityId originatorId, AlarmUpdateMsg alarmUpdateMsg) { - return JacksonUtil.fromString(alarmUpdateMsg.getEntity(), Alarm.class, true); - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmProcessor.java index d4f820b6c3..8d31c07960 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmProcessor.java @@ -16,23 +16,13 @@ package org.thingsboard.server.service.edge.rpc.processor.alarm; import com.google.common.util.concurrent.ListenableFuture; -import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.gen.edge.v1.AlarmCommentUpdateMsg; import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; -import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.service.edge.rpc.processor.EdgeProcessor; public interface AlarmProcessor extends EdgeProcessor { ListenableFuture processAlarmMsgFromEdge(TenantId tenantId, EdgeId edgeId, AlarmUpdateMsg alarmUpdateMsg); - DownlinkMsg convertAlarmEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion); - - ListenableFuture processAlarmCommentMsgFromEdge(TenantId tenantId, EdgeId edgeId, AlarmCommentUpdateMsg alarmCommentUpdateMsg); - - DownlinkMsg convertAlarmCommentEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion); - } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java index 195b569ab4..d806049888 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.service.edge.rpc.processor.alarm; +import com.fasterxml.jackson.databind.JsonNode; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; @@ -24,16 +25,24 @@ import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmComment; import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest; import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.AlarmId; +import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageDataIterableByTenantIdEntityId; import org.thingsboard.server.dao.alarm.AlarmCommentDao; import org.thingsboard.server.gen.edge.v1.AlarmCommentUpdateMsg; import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; +import static org.thingsboard.server.dao.edge.BaseRelatedEdgesService.RELATED_EDGES_CACHE_ITEMS; + @Slf4j public abstract class BaseAlarmProcessor extends BaseEdgeProcessor { @@ -43,8 +52,7 @@ public abstract class BaseAlarmProcessor extends BaseEdgeProcessor { public ListenableFuture processAlarmMsg(TenantId tenantId, AlarmUpdateMsg alarmUpdateMsg) { log.trace("[{}] processAlarmMsg [{}]", tenantId, alarmUpdateMsg); AlarmId alarmId = new AlarmId(new UUID(alarmUpdateMsg.getIdMSB(), alarmUpdateMsg.getIdLSB())); - EntityId originatorId = getAlarmOriginatorFromMsg(tenantId, alarmUpdateMsg); - Alarm alarm = constructAlarmFromUpdateMsg(tenantId, alarmId, originatorId, alarmUpdateMsg); + Alarm alarm = JacksonUtil.fromString(alarmUpdateMsg.getEntity(), Alarm.class, true); if (alarm == null) { throw new RuntimeException("[{" + tenantId + "}] alarmUpdateMsg {" + alarmUpdateMsg + "} cannot be converted to alarm"); } @@ -124,9 +132,18 @@ public abstract class BaseAlarmProcessor extends BaseEdgeProcessor { return Futures.immediateFuture(null); } - - protected abstract EntityId getAlarmOriginatorFromMsg(TenantId tenantId, AlarmUpdateMsg alarmUpdateMsg); - - protected abstract Alarm constructAlarmFromUpdateMsg(TenantId tenantId, AlarmId alarmId, EntityId originatorId, AlarmUpdateMsg alarmUpdateMsg); + protected List> pushEventToAllRelatedEdges(TenantId tenantId, EntityId originatorId, AlarmId alarmId, + EdgeEventActionType actionType, JsonNode body, EdgeId sourceEdgeId, + EdgeEventType edgeEventType) { + List> futures = new ArrayList<>(); + PageDataIterableByTenantIdEntityId edgeIds = + new PageDataIterableByTenantIdEntityId<>(edgeCtx.getEdgeService()::findRelatedEdgeIdsByEntityId, tenantId, originatorId, RELATED_EDGES_CACHE_ITEMS); + for (EdgeId relatedEdgeId : edgeIds) { + if (!relatedEdgeId.equals(sourceEdgeId)) { + futures.add(saveEdgeEvent(tenantId, relatedEdgeId, edgeEventType, actionType, alarmId, body)); + } + } + return futures; + } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/comment/AlarmCommentEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/comment/AlarmCommentEdgeProcessor.java new file mode 100644 index 0000000000..90c2e8031e --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/comment/AlarmCommentEdgeProcessor.java @@ -0,0 +1,98 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc.processor.alarm.comment; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.EdgeUtils; +import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmComment; +import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.edge.EdgeEventType; +import org.thingsboard.server.common.data.id.AlarmId; +import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.gen.edge.v1.AlarmCommentUpdateMsg; +import org.thingsboard.server.gen.edge.v1.DownlinkMsg; +import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; +import org.thingsboard.server.service.edge.rpc.processor.alarm.BaseAlarmProcessor; + +import java.util.List; +import java.util.UUID; + +@Slf4j +@Component +@TbCoreComponent +public class AlarmCommentEdgeProcessor extends BaseAlarmProcessor implements AlarmCommentProcessor { + + @Override + public ListenableFuture processAlarmCommentMsgFromEdge(TenantId tenantId, EdgeId edgeId, AlarmCommentUpdateMsg alarmCommentUpdateMsg) { + log.trace("[{}] processAlarmCommentMsgFromEdge [{}]", tenantId, alarmCommentUpdateMsg); + try { + edgeSynchronizationManager.getEdgeId().set(edgeId); + return processAlarmCommentMsg(tenantId, alarmCommentUpdateMsg); + } finally { + edgeSynchronizationManager.getEdgeId().remove(); + } + } + + @Override + public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { + UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); + switch (edgeEvent.getAction()) { + case ADDED_COMMENT: + case UPDATED_COMMENT: + case DELETED_COMMENT: + AlarmComment alarmComment = JacksonUtil.convertValue(edgeEvent.getBody(), AlarmComment.class); + if (alarmComment != null) { + return DownlinkMsg.newBuilder() + .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) + .addAlarmCommentUpdateMsg(EdgeMsgConstructorUtils.constructAlarmCommentUpdatedMsg(msgType, alarmComment)) + .build(); + } + default: + return null; + } + } + + @Override + public ListenableFuture processEntityNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { + EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); + AlarmId alarmId = new AlarmId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); + EdgeId originatorEdgeId = safeGetEdgeId(edgeNotificationMsg.getOriginatorEdgeIdMSB(), edgeNotificationMsg.getOriginatorEdgeIdLSB()); + AlarmComment alarmComment = JacksonUtil.fromString(edgeNotificationMsg.getBody(), AlarmComment.class); + if (alarmComment == null) { + return Futures.immediateFuture(null); + } + Alarm alarmById = edgeCtx.getAlarmService().findAlarmById(tenantId, new AlarmId(alarmComment.getAlarmId().getId())); + List> delFutures = pushEventToAllRelatedEdges(tenantId, alarmById.getOriginator(), + alarmId, actionType, JacksonUtil.valueToTree(alarmComment), originatorEdgeId, EdgeEventType.ALARM_COMMENT); + return Futures.transform(Futures.allAsList(delFutures), voids -> null, dbCallbackExecutorService); + } + + @Override + public EdgeEventType getEdgeEventType() { + return EdgeEventType.ALARM_COMMENT; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/alarm/BaseAlarmMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/comment/AlarmCommentProcessor.java similarity index 54% rename from application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/alarm/BaseAlarmMsgConstructor.java rename to application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/comment/AlarmCommentProcessor.java index c9af86e8cc..5be3c0b806 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/alarm/BaseAlarmMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/comment/AlarmCommentProcessor.java @@ -13,17 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.edge.rpc.constructor.alarm; +package org.thingsboard.server.service.edge.rpc.processor.alarm.comment; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.alarm.AlarmComment; +import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.gen.edge.v1.AlarmCommentUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.service.edge.rpc.processor.EdgeProcessor; -public abstract class BaseAlarmMsgConstructor implements AlarmMsgConstructor { +public interface AlarmCommentProcessor extends EdgeProcessor { + + ListenableFuture processAlarmCommentMsgFromEdge(TenantId tenantId, EdgeId edgeId, AlarmCommentUpdateMsg alarmCommentUpdateMsg); - @Override - public AlarmCommentUpdateMsg constructAlarmCommentUpdatedMsg(UpdateMsgType msgType, AlarmComment alarmComment) { - return AlarmCommentUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(alarmComment)).build(); - } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java index 56a4864763..51815bc3aa 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java @@ -19,6 +19,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.data.util.Pair; +import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.asset.Asset; @@ -28,20 +29,23 @@ import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.AssetId; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.service.edge.rpc.constructor.asset.AssetMsgConstructor; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; import java.util.UUID; @Slf4j -public abstract class AssetEdgeProcessor extends BaseAssetProcessor implements AssetProcessor { +@Component +@TbCoreComponent +public class AssetEdgeProcessor extends BaseAssetProcessor implements AssetProcessor { @Override public ListenableFuture processAssetMsgFromEdge(TenantId tenantId, Edge edge, AssetUpdateMsg assetUpdateMsg) { @@ -103,21 +107,20 @@ public abstract class AssetEdgeProcessor extends BaseAssetProcessor implements A } @Override - public DownlinkMsg convertAssetEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { + public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { AssetId assetId = new AssetId(edgeEvent.getEntityId()); - var msgConstructor = (AssetMsgConstructor) edgeCtx.getAssetMsgConstructorFactory().getMsgConstructorByEdgeVersion(edgeVersion); switch (edgeEvent.getAction()) { case ADDED, UPDATED, ASSIGNED_TO_EDGE, ASSIGNED_TO_CUSTOMER, UNASSIGNED_FROM_CUSTOMER -> { Asset asset = edgeCtx.getAssetService().findAssetById(edgeEvent.getTenantId(), assetId); if (asset != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - AssetUpdateMsg assetUpdateMsg = msgConstructor.constructAssetUpdatedMsg(msgType, asset); + AssetUpdateMsg assetUpdateMsg = EdgeMsgConstructorUtils.constructAssetUpdatedMsg(msgType, asset); DownlinkMsg.Builder builder = DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addAssetUpdateMsg(assetUpdateMsg); if (UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE.equals(msgType)) { AssetProfile assetProfile = edgeCtx.getAssetProfileService().findAssetProfileById(edgeEvent.getTenantId(), asset.getAssetProfileId()); - builder.addAssetProfileUpdateMsg(msgConstructor.constructAssetProfileUpdatedMsg(msgType, assetProfile)); + builder.addAssetProfileUpdateMsg(EdgeMsgConstructorUtils.constructAssetProfileUpdatedMsg(msgType, assetProfile)); } return builder.build(); } @@ -125,11 +128,22 @@ public abstract class AssetEdgeProcessor extends BaseAssetProcessor implements A case DELETED, UNASSIGNED_FROM_EDGE -> { return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addAssetUpdateMsg(msgConstructor.constructAssetDeleteMsg(assetId)) + .addAssetUpdateMsg(EdgeMsgConstructorUtils.constructAssetDeleteMsg(assetId)) .build(); } } return null; } + @Override + protected void setCustomerId(TenantId tenantId, CustomerId customerId, Asset asset, AssetUpdateMsg assetUpdateMsg) { + CustomerId customerUUID = asset.getCustomerId() != null ? asset.getCustomerId() : customerId; + asset.setCustomerId(customerUUID); + } + + @Override + public EdgeEventType getEdgeEventType() { + return EdgeEventType.ASSET; + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessorV1.java deleted file mode 100644 index 8cd2de1cab..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessorV1.java +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.processor.asset; - -import com.datastax.oss.driver.api.core.uuid.Uuids; -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.asset.Asset; -import org.thingsboard.server.common.data.id.AssetId; -import org.thingsboard.server.common.data.id.AssetProfileId; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg; -import org.thingsboard.server.queue.util.TbCoreComponent; - -import java.util.UUID; - -@Component -@TbCoreComponent -public class AssetEdgeProcessorV1 extends AssetEdgeProcessor { - - @Override - protected Asset constructAssetFromUpdateMsg(TenantId tenantId, AssetId assetId, AssetUpdateMsg assetUpdateMsg) { - Asset asset = new Asset(); - asset.setTenantId(tenantId); - asset.setName(assetUpdateMsg.getName()); - asset.setCreatedTime(Uuids.unixTimestamp(assetId.getId())); - asset.setType(assetUpdateMsg.getType()); - asset.setLabel(assetUpdateMsg.hasLabel() ? assetUpdateMsg.getLabel() : null); - asset.setAdditionalInfo(assetUpdateMsg.hasAdditionalInfo() - ? JacksonUtil.toJsonNode(assetUpdateMsg.getAdditionalInfo()) : null); - - UUID assetProfileUUID = safeGetUUID(assetUpdateMsg.getAssetProfileIdMSB(), assetUpdateMsg.getAssetProfileIdLSB()); - asset.setAssetProfileId(assetProfileUUID != null ? new AssetProfileId(assetProfileUUID) : null); - - CustomerId customerId = safeGetCustomerId(assetUpdateMsg.getCustomerIdMSB(), assetUpdateMsg.getCustomerIdLSB()); - asset.setCustomerId(customerId); - return asset; - } - - @Override - protected void setCustomerId(TenantId tenantId, CustomerId customerId, Asset asset, AssetUpdateMsg assetUpdateMsg) { - CustomerId customerUUID = safeGetCustomerId(assetUpdateMsg.getCustomerIdMSB(), assetUpdateMsg.getCustomerIdLSB()); - asset.setCustomerId(customerUUID != null ? customerUUID : customerId); - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessorV2.java deleted file mode 100644 index 021507f5d7..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessorV2.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.processor.asset; - -import org.springframework.context.annotation.Primary; -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.asset.Asset; -import org.thingsboard.server.common.data.id.AssetId; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Primary -@Component -@TbCoreComponent -public class AssetEdgeProcessorV2 extends AssetEdgeProcessor { - - @Override - protected Asset constructAssetFromUpdateMsg(TenantId tenantId, AssetId assetId, AssetUpdateMsg assetUpdateMsg) { - return JacksonUtil.fromString(assetUpdateMsg.getEntity(), Asset.class, true); - } - - @Override - protected void setCustomerId(TenantId tenantId, CustomerId customerId, Asset asset, AssetUpdateMsg assetUpdateMsg) { - CustomerId customerUUID = asset.getCustomerId() != null ? asset.getCustomerId() : customerId; - asset.setCustomerId(customerUUID); - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProcessor.java index f6b015701c..0ae21df039 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProcessor.java @@ -17,17 +17,12 @@ package org.thingsboard.server.service.edge.rpc.processor.asset; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg; -import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.service.edge.rpc.processor.EdgeProcessor; public interface AssetProcessor extends EdgeProcessor { ListenableFuture processAssetMsgFromEdge(TenantId tenantId, Edge edge, AssetUpdateMsg assetUpdateMsg); - DownlinkMsg convertAssetEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion); - } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/BaseAssetProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/BaseAssetProcessor.java index 9ebcd74888..7618a2287c 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/BaseAssetProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/BaseAssetProcessor.java @@ -18,6 +18,7 @@ package org.thingsboard.server.service.edge.rpc.processor.asset; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.util.Pair; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.AssetId; @@ -38,7 +39,7 @@ public abstract class BaseAssetProcessor extends BaseEdgeProcessor { boolean assetNameUpdated = false; assetCreationLock.lock(); try { - Asset asset = constructAssetFromUpdateMsg(tenantId, assetId, assetUpdateMsg); + Asset asset = JacksonUtil.fromString(assetUpdateMsg.getEntity(), Asset.class, true); if (asset == null) { throw new RuntimeException("[{" + tenantId + "}] assetUpdateMsg {" + assetUpdateMsg + " } cannot be converted to asset"); } @@ -74,8 +75,6 @@ public abstract class BaseAssetProcessor extends BaseEdgeProcessor { return Pair.of(created, assetNameUpdated); } - protected abstract Asset constructAssetFromUpdateMsg(TenantId tenantId, AssetId assetId, AssetUpdateMsg assetUpdateMsg); - protected abstract void setCustomerId(TenantId tenantId, CustomerId customerId, Asset asset, AssetUpdateMsg assetUpdateMsg); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileEdgeProcessor.java index 1998def9fd..53f35d3aac 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileEdgeProcessor.java @@ -19,6 +19,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.data.util.Pair; +import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.asset.AssetProfile; @@ -27,21 +28,24 @@ import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.AssetProfileId; -import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.DashboardId; +import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.service.edge.rpc.constructor.asset.AssetMsgConstructor; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; import java.util.UUID; @Slf4j -public abstract class AssetProfileEdgeProcessor extends BaseAssetProfileProcessor implements AssetProfileProcessor { +@Component +@TbCoreComponent +public class AssetProfileEdgeProcessor extends BaseAssetProfileProcessor implements AssetProfileProcessor { @Override public ListenableFuture processAssetProfileMsgFromEdge(TenantId tenantId, Edge edge, AssetProfileUpdateMsg assetProfileUpdateMsg) { @@ -90,15 +94,14 @@ public abstract class AssetProfileEdgeProcessor extends BaseAssetProfileProcesso } @Override - public DownlinkMsg convertAssetProfileEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { + public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { AssetProfileId assetProfileId = new AssetProfileId(edgeEvent.getEntityId()); - var msgConstructor = (AssetMsgConstructor) edgeCtx.getAssetMsgConstructorFactory().getMsgConstructorByEdgeVersion(edgeVersion); switch (edgeEvent.getAction()) { case ADDED, UPDATED -> { AssetProfile assetProfile = edgeCtx.getAssetProfileService().findAssetProfileById(edgeEvent.getTenantId(), assetProfileId); if (assetProfile != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - AssetProfileUpdateMsg assetProfileUpdateMsg = msgConstructor.constructAssetProfileUpdatedMsg(msgType, assetProfile); + AssetProfileUpdateMsg assetProfileUpdateMsg = EdgeMsgConstructorUtils.constructAssetProfileUpdatedMsg(msgType, assetProfile); return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addAssetProfileUpdateMsg(assetProfileUpdateMsg) @@ -106,7 +109,7 @@ public abstract class AssetProfileEdgeProcessor extends BaseAssetProfileProcesso } } case DELETED -> { - AssetProfileUpdateMsg assetProfileUpdateMsg = msgConstructor.constructAssetProfileDeleteMsg(assetProfileId); + AssetProfileUpdateMsg assetProfileUpdateMsg = EdgeMsgConstructorUtils.constructAssetProfileDeleteMsg(assetProfileId); return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addAssetProfileUpdateMsg(assetProfileUpdateMsg) @@ -116,4 +119,26 @@ public abstract class AssetProfileEdgeProcessor extends BaseAssetProfileProcesso return null; } + @Override + protected void setDefaultRuleChainId(TenantId tenantId, AssetProfile assetProfile, RuleChainId ruleChainId) { + assetProfile.setDefaultRuleChainId(ruleChainId); + } + + @Override + protected void setDefaultEdgeRuleChainId(AssetProfile assetProfile, RuleChainId ruleChainId, AssetProfileUpdateMsg assetProfileUpdateMsg) { + UUID defaultEdgeRuleChainUUID = ruleChainId != null ? ruleChainId.getId() : null; + assetProfile.setDefaultEdgeRuleChainId(defaultEdgeRuleChainUUID != null ? new RuleChainId(defaultEdgeRuleChainUUID) : null); + } + + @Override + protected void setDefaultDashboardId(TenantId tenantId, DashboardId dashboardId, AssetProfile assetProfile, AssetProfileUpdateMsg assetProfileUpdateMsg) { + UUID defaultDashboardUUID = assetProfile.getDefaultDashboardId() != null ? assetProfile.getDefaultDashboardId().getId() : (dashboardId != null ? dashboardId.getId() : null); + assetProfile.setDefaultDashboardId(defaultDashboardUUID != null ? new DashboardId(defaultDashboardUUID) : null); + } + + @Override + public EdgeEventType getEdgeEventType() { + return EdgeEventType.ASSET_PROFILE; + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileEdgeProcessorFactory.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileEdgeProcessorFactory.java deleted file mode 100644 index 8d31a9d39c..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileEdgeProcessorFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.processor.asset.profile; - -import org.springframework.stereotype.Component; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessorFactory; - -@Component -@TbCoreComponent -public class AssetProfileEdgeProcessorFactory extends BaseEdgeProcessorFactory { - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileEdgeProcessorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileEdgeProcessorV1.java deleted file mode 100644 index 510b5d834e..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileEdgeProcessorV1.java +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.processor.asset.profile; - -import com.datastax.oss.driver.api.core.uuid.Uuids; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.asset.AssetProfile; -import org.thingsboard.server.common.data.id.AssetProfileId; -import org.thingsboard.server.common.data.id.DashboardId; -import org.thingsboard.server.common.data.id.RuleChainId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg; -import org.thingsboard.server.queue.util.TbCoreComponent; - -import java.nio.charset.StandardCharsets; -import java.util.UUID; - -@Component -@TbCoreComponent -public class AssetProfileEdgeProcessorV1 extends AssetProfileEdgeProcessor { - - @Override - protected AssetProfile constructAssetProfileFromUpdateMsg(TenantId tenantId, AssetProfileId assetProfileId, AssetProfileUpdateMsg assetProfileUpdateMsg) { - AssetProfile assetProfile = new AssetProfile(); - assetProfile.setTenantId(tenantId); - assetProfile.setName(assetProfileUpdateMsg.getName()); - assetProfile.setCreatedTime(Uuids.unixTimestamp(assetProfileId.getId())); - assetProfile.setDefault(assetProfileUpdateMsg.getDefault()); - assetProfile.setDefaultQueueName(assetProfileUpdateMsg.hasDefaultQueueName() ? assetProfileUpdateMsg.getDefaultQueueName() : null); - assetProfile.setDescription(assetProfileUpdateMsg.hasDescription() ? assetProfileUpdateMsg.getDescription() : null); - assetProfile.setImage(assetProfileUpdateMsg.hasImage() - ? new String(assetProfileUpdateMsg.getImage().toByteArray(), StandardCharsets.UTF_8) : null); - return assetProfile; - } - - @Override - protected void setDefaultRuleChainId(TenantId tenantId, AssetProfile assetProfile, RuleChainId ruleChainId) { - assetProfile.setDefaultRuleChainId(ruleChainId); - } - - @Override - protected void setDefaultEdgeRuleChainId(AssetProfile assetProfile, RuleChainId ruleChainId, AssetProfileUpdateMsg assetProfileUpdateMsg) { - UUID defaultEdgeRuleChainUUID = safeGetUUID(assetProfileUpdateMsg.getDefaultRuleChainIdMSB(), assetProfileUpdateMsg.getDefaultRuleChainIdLSB()); - assetProfile.setDefaultEdgeRuleChainId(defaultEdgeRuleChainUUID != null ? new RuleChainId(defaultEdgeRuleChainUUID) : ruleChainId); - } - - @Override - protected void setDefaultDashboardId(TenantId tenantId, DashboardId dashboardId, AssetProfile assetProfile, AssetProfileUpdateMsg assetProfileUpdateMsg) { - UUID defaultDashboardUUID = safeGetUUID(assetProfileUpdateMsg.getDefaultDashboardIdMSB(), assetProfileUpdateMsg.getDefaultDashboardIdLSB()); - assetProfile.setDefaultDashboardId(defaultDashboardUUID != null ? new DashboardId(defaultDashboardUUID) : dashboardId); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileEdgeProcessorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileEdgeProcessorV2.java deleted file mode 100644 index 9983026213..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileEdgeProcessorV2.java +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.processor.asset.profile; - -import org.springframework.context.annotation.Primary; -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.asset.AssetProfile; -import org.thingsboard.server.common.data.id.AssetProfileId; -import org.thingsboard.server.common.data.id.DashboardId; -import org.thingsboard.server.common.data.id.RuleChainId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg; -import org.thingsboard.server.queue.util.TbCoreComponent; - -import java.util.UUID; - -@Primary -@Component -@TbCoreComponent -public class AssetProfileEdgeProcessorV2 extends AssetProfileEdgeProcessor { - - @Override - protected AssetProfile constructAssetProfileFromUpdateMsg(TenantId tenantId, AssetProfileId assetProfileId, AssetProfileUpdateMsg assetProfileUpdateMsg) { - return JacksonUtil.fromString(assetProfileUpdateMsg.getEntity(), AssetProfile.class, true); - } - - @Override - protected void setDefaultRuleChainId(TenantId tenantId, AssetProfile assetProfile, RuleChainId ruleChainId) { - assetProfile.setDefaultRuleChainId(ruleChainId); - } - - @Override - protected void setDefaultEdgeRuleChainId(AssetProfile assetProfile, RuleChainId ruleChainId, AssetProfileUpdateMsg assetProfileUpdateMsg) { - UUID defaultEdgeRuleChainUUID = ruleChainId != null ? ruleChainId.getId() : null; - assetProfile.setDefaultEdgeRuleChainId(defaultEdgeRuleChainUUID != null ? new RuleChainId(defaultEdgeRuleChainUUID) : null); - } - - @Override - protected void setDefaultDashboardId(TenantId tenantId, DashboardId dashboardId, AssetProfile assetProfile, AssetProfileUpdateMsg assetProfileUpdateMsg) { - UUID defaultDashboardUUID = assetProfile.getDefaultDashboardId() != null ? assetProfile.getDefaultDashboardId().getId() : (dashboardId != null ? dashboardId.getId() : null); - assetProfile.setDefaultDashboardId(defaultDashboardUUID != null ? new DashboardId(defaultDashboardUUID) : null); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileProcessor.java index 83051c519c..d5670924c9 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileProcessor.java @@ -17,17 +17,12 @@ package org.thingsboard.server.service.edge.rpc.processor.asset.profile; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg; -import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.service.edge.rpc.processor.EdgeProcessor; public interface AssetProfileProcessor extends EdgeProcessor { ListenableFuture processAssetProfileMsgFromEdge(TenantId tenantId, Edge edge, AssetProfileUpdateMsg assetProfileUpdateMsg); - DownlinkMsg convertAssetProfileEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion); - } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/BaseAssetProfileProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/BaseAssetProfileProcessor.java index 2b1440a480..af243e3afe 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/BaseAssetProfileProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/BaseAssetProfileProcessor.java @@ -18,6 +18,7 @@ package org.thingsboard.server.service.edge.rpc.processor.asset.profile; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.util.Pair; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.asset.AssetProfile; import org.thingsboard.server.common.data.id.AssetProfileId; @@ -39,7 +40,7 @@ public abstract class BaseAssetProfileProcessor extends BaseEdgeProcessor { boolean assetProfileNameUpdated = false; assetCreationLock.lock(); try { - AssetProfile assetProfile = constructAssetProfileFromUpdateMsg(tenantId, assetProfileId, assetProfileUpdateMsg); + AssetProfile assetProfile = JacksonUtil.fromString(assetProfileUpdateMsg.getEntity(), AssetProfile.class, true); if (assetProfile == null) { throw new RuntimeException("[{" + tenantId + "}] assetProfileUpdateMsg {" + assetProfileUpdateMsg + "} cannot be converted to asset profile"); } @@ -81,8 +82,6 @@ public abstract class BaseAssetProfileProcessor extends BaseEdgeProcessor { return Pair.of(created, assetProfileNameUpdated); } - protected abstract AssetProfile constructAssetProfileFromUpdateMsg(TenantId tenantId, AssetProfileId assetProfileId, AssetProfileUpdateMsg assetProfileUpdateMsg); - protected abstract void setDefaultRuleChainId(TenantId tenantId, AssetProfile assetProfile, RuleChainId ruleChainId); protected abstract void setDefaultEdgeRuleChainId(AssetProfile assetProfile, RuleChainId ruleChainId, AssetProfileUpdateMsg assetProfileUpdateMsg); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/customer/CustomerEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/customer/CustomerEdgeProcessor.java index ddf6f25728..fc70e8964b 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/customer/CustomerEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/customer/CustomerEdgeProcessor.java @@ -18,7 +18,6 @@ package org.thingsboard.server.service.edge.rpc.processor.customer; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.EdgeUtils; @@ -33,35 +32,30 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.gen.edge.v1.CustomerUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.constructor.customer.CustomerMsgConstructor; -import org.thingsboard.server.service.edge.rpc.constructor.customer.CustomerMsgConstructorFactory; +import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; import java.util.ArrayList; import java.util.List; import java.util.UUID; -@Component @Slf4j +@Component @TbCoreComponent public class CustomerEdgeProcessor extends BaseEdgeProcessor { - @Autowired - private CustomerMsgConstructorFactory customerMsgConstructorFactory; - - public DownlinkMsg convertCustomerEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { + @Override + public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { CustomerId customerId = new CustomerId(edgeEvent.getEntityId()); - var msgConstructor = (CustomerMsgConstructor) customerMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion); switch (edgeEvent.getAction()) { case ADDED, UPDATED -> { Customer customer = edgeCtx.getCustomerService().findCustomerById(edgeEvent.getTenantId(), customerId); if (customer != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - CustomerUpdateMsg customerUpdateMsg = msgConstructor.constructCustomerUpdatedMsg(msgType, customer); + CustomerUpdateMsg customerUpdateMsg = EdgeMsgConstructorUtils.constructCustomerUpdatedMsg(msgType, customer); return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addCustomerUpdateMsg(customerUpdateMsg) @@ -69,7 +63,7 @@ public class CustomerEdgeProcessor extends BaseEdgeProcessor { } } case DELETED -> { - CustomerUpdateMsg customerUpdateMsg = msgConstructor.constructCustomerDeleteMsg(customerId); + CustomerUpdateMsg customerUpdateMsg = EdgeMsgConstructorUtils.constructCustomerDeleteMsg(customerId); return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addCustomerUpdateMsg(customerUpdateMsg) @@ -79,7 +73,8 @@ public class CustomerEdgeProcessor extends BaseEdgeProcessor { return null; } - public ListenableFuture processCustomerNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { + @Override + public ListenableFuture processEntityNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType()); UUID uuid = new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB()); @@ -100,4 +95,9 @@ public class CustomerEdgeProcessor extends BaseEdgeProcessor { } } + @Override + public EdgeEventType getEdgeEventType() { + return EdgeEventType.CUSTOMER; + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/BaseDashboardProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/BaseDashboardProcessor.java index 52277b8527..bf56efa788 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/BaseDashboardProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/BaseDashboardProcessor.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.edge.rpc.processor.dashboard; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.ShortCustomerInfo; import org.thingsboard.server.common.data.id.CustomerId; @@ -26,6 +27,7 @@ import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; +import java.util.HashSet; import java.util.Set; @Slf4j @@ -36,58 +38,68 @@ public abstract class BaseDashboardProcessor extends BaseEdgeProcessor { protected boolean saveOrUpdateDashboard(TenantId tenantId, DashboardId dashboardId, DashboardUpdateMsg dashboardUpdateMsg, CustomerId customerId) { boolean created = false; - Dashboard dashboard = constructDashboardFromUpdateMsg(tenantId, dashboardId, dashboardUpdateMsg); + Dashboard dashboard = JacksonUtil.fromString(dashboardUpdateMsg.getEntity(), Dashboard.class, true); if (dashboard == null) { throw new RuntimeException("[{" + tenantId + "}] dashboardUpdateMsg {" + dashboardUpdateMsg + "} cannot be converted to dashboard"); } - Set assignedCustomers = null; + Set newAssignedCustomers = new HashSet<>(); + if (dashboard.getAssignedCustomers() != null && !dashboard.getAssignedCustomers().isEmpty()) { + newAssignedCustomers.addAll(dashboard.getAssignedCustomers()); + } Dashboard dashboardById = edgeCtx.getDashboardService().findDashboardById(tenantId, dashboardId); if (dashboardById == null) { created = true; dashboard.setId(null); + dashboard.setAssignedCustomers(null); } else { dashboard.setId(dashboardId); - assignedCustomers = filterNonExistingCustomers(tenantId, dashboardById.getAssignedCustomers()); + dashboard.setAssignedCustomers(dashboardById.getAssignedCustomers()); } dashboardValidator.validate(dashboard, Dashboard::getTenantId); if (created) { dashboard.setId(dashboardId); } - Set msgAssignedCustomers = filterNonExistingCustomers(tenantId, dashboard.getAssignedCustomers()); - if (msgAssignedCustomers != null) { - if (assignedCustomers == null) { - assignedCustomers = msgAssignedCustomers; - } else { - assignedCustomers.addAll(msgAssignedCustomers); + + Dashboard savedDashboard = edgeCtx.getDashboardService().saveDashboard(dashboard, false); + + updateDashboardAssignments(tenantId, dashboardById, savedDashboard, newAssignedCustomers); + + return created; + } + + private void updateDashboardAssignments(TenantId tenantId, Dashboard dashboardById, Dashboard savedDashboard, Set newAssignedCustomers) { + Set currentAssignedCustomers = new HashSet<>(); + if (dashboardById != null) { + if (dashboardById.getAssignedCustomers() != null) { + currentAssignedCustomers.addAll(dashboardById.getAssignedCustomers()); } } - dashboard.setAssignedCustomers(assignedCustomers); - Dashboard savedDashboard = edgeCtx.getDashboardService().saveDashboard(dashboard, false); - if (msgAssignedCustomers != null && !msgAssignedCustomers.isEmpty()) { - for (ShortCustomerInfo assignedCustomer : msgAssignedCustomers) { - if (assignedCustomer.getCustomerId().equals(customerId)) { - edgeCtx.getDashboardService().assignDashboardToCustomer(tenantId, savedDashboard.getId(), assignedCustomer.getCustomerId()); - } + + newAssignedCustomers = filterNonExistingCustomers(tenantId, currentAssignedCustomers, newAssignedCustomers); + + Set addedCustomerIds = new HashSet<>(); + Set removedCustomerIds = new HashSet<>(); + for (ShortCustomerInfo newAssignedCustomer : newAssignedCustomers) { + if (!savedDashboard.isAssignedToCustomer(newAssignedCustomer.getCustomerId())) { + addedCustomerIds.add(newAssignedCustomer.getCustomerId()); } - } else { - unassignCustomersFromDashboard(tenantId, savedDashboard, customerId); } - return created; - } - private void unassignCustomersFromDashboard(TenantId tenantId, Dashboard dashboard, CustomerId customerId) { - if (dashboard.getAssignedCustomers() != null && !dashboard.getAssignedCustomers().isEmpty()) { - for (ShortCustomerInfo assignedCustomer : dashboard.getAssignedCustomers()) { - if (assignedCustomer.getCustomerId().equals(customerId)) { - edgeCtx.getDashboardService().unassignDashboardFromCustomer(tenantId, dashboard.getId(), assignedCustomer.getCustomerId()); - } + for (ShortCustomerInfo currentAssignedCustomer : currentAssignedCustomers) { + if (!newAssignedCustomers.contains(currentAssignedCustomer)) { + removedCustomerIds.add(currentAssignedCustomer.getCustomerId()); } } - } - protected abstract Dashboard constructDashboardFromUpdateMsg(TenantId tenantId, DashboardId dashboardId, DashboardUpdateMsg dashboardUpdateMsg); + for (CustomerId customerIdToAdd : addedCustomerIds) { + edgeCtx.getDashboardService().assignDashboardToCustomer(tenantId, savedDashboard.getId(), customerIdToAdd); + } + for (CustomerId customerIdToRemove : removedCustomerIds) { + edgeCtx.getDashboardService().unassignDashboardFromCustomer(tenantId, savedDashboard.getId(), customerIdToRemove); + } + } - protected abstract Set filterNonExistingCustomers(TenantId tenantId, Set assignedCustomers); + protected abstract Set filterNonExistingCustomers(TenantId tenantId, Set currentAssignedCustomers, Set newAssignedCustomers); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessor.java index cb54977c3c..fd933d4b5c 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessor.java @@ -18,13 +18,14 @@ package org.thingsboard.server.service.edge.rpc.processor.dashboard; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.ShortCustomerInfo; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbMsgType; @@ -32,19 +33,17 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.service.edge.rpc.constructor.dashboard.DashboardMsgConstructor; -import org.thingsboard.server.service.edge.rpc.constructor.dashboard.DashboardMsgConstructorFactory; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; import java.util.Set; import java.util.UUID; @Slf4j -public abstract class DashboardEdgeProcessor extends BaseDashboardProcessor implements DashboardProcessor { - - @Autowired - private DashboardMsgConstructorFactory dashboardMsgConstructorFactory; +@Component +@TbCoreComponent +public class DashboardEdgeProcessor extends BaseDashboardProcessor implements DashboardProcessor { @Override public ListenableFuture processDashboardMsgFromEdge(TenantId tenantId, Edge edge, DashboardUpdateMsg dashboardUpdateMsg) { @@ -101,15 +100,14 @@ public abstract class DashboardEdgeProcessor extends BaseDashboardProcessor impl } @Override - public DownlinkMsg convertDashboardEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { + public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { DashboardId dashboardId = new DashboardId(edgeEvent.getEntityId()); - var msgConstructor = (DashboardMsgConstructor) dashboardMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion); switch (edgeEvent.getAction()) { case ADDED, UPDATED, ASSIGNED_TO_EDGE, ASSIGNED_TO_CUSTOMER, UNASSIGNED_FROM_CUSTOMER -> { Dashboard dashboard = edgeCtx.getDashboardService().findDashboardById(edgeEvent.getTenantId(), dashboardId); if (dashboard != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - DashboardUpdateMsg dashboardUpdateMsg = msgConstructor.constructDashboardUpdatedMsg(msgType, dashboard); + DashboardUpdateMsg dashboardUpdateMsg = EdgeMsgConstructorUtils.constructDashboardUpdatedMsg(msgType, dashboard); return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addDashboardUpdateMsg(dashboardUpdateMsg) @@ -117,7 +115,7 @@ public abstract class DashboardEdgeProcessor extends BaseDashboardProcessor impl } } case DELETED, UNASSIGNED_FROM_EDGE -> { - DashboardUpdateMsg dashboardUpdateMsg = msgConstructor.constructDashboardDeleteMsg(dashboardId); + DashboardUpdateMsg dashboardUpdateMsg = EdgeMsgConstructorUtils.constructDashboardDeleteMsg(dashboardId); return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addDashboardUpdateMsg(dashboardUpdateMsg) @@ -128,9 +126,14 @@ public abstract class DashboardEdgeProcessor extends BaseDashboardProcessor impl } @Override - protected Set filterNonExistingCustomers(TenantId tenantId, Set assignedCustomers) { - // do nothing on cloud - return assignedCustomers; + protected Set filterNonExistingCustomers(TenantId tenantId, Set currentAssignedCustomers, Set newAssignedCustomers) { + newAssignedCustomers.addAll(currentAssignedCustomers); + return newAssignedCustomers; + } + + @Override + public EdgeEventType getEdgeEventType() { + return EdgeEventType.DASHBOARD; } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessorFactory.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessorFactory.java deleted file mode 100644 index 0d2e9c16fe..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessorFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.processor.dashboard; - -import org.springframework.stereotype.Component; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessorFactory; - -@Component -@TbCoreComponent -public class DashboardEdgeProcessorFactory extends BaseEdgeProcessorFactory { - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessorV1.java deleted file mode 100644 index 20ab6019bf..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessorV1.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.processor.dashboard; - -import com.datastax.oss.driver.api.core.uuid.Uuids; -import com.fasterxml.jackson.core.type.TypeReference; -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.Dashboard; -import org.thingsboard.server.common.data.ShortCustomerInfo; -import org.thingsboard.server.common.data.id.DashboardId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg; -import org.thingsboard.server.queue.util.TbCoreComponent; - -import java.util.Set; - -@Component -@TbCoreComponent -public class DashboardEdgeProcessorV1 extends DashboardEdgeProcessor { - - @Override - protected Dashboard constructDashboardFromUpdateMsg(TenantId tenantId, DashboardId dashboardId, DashboardUpdateMsg dashboardUpdateMsg) { - Dashboard dashboard = new Dashboard(); - dashboard.setTenantId(tenantId); - dashboard.setCreatedTime(Uuids.unixTimestamp(dashboardId.getId())); - dashboard.setTitle(dashboardUpdateMsg.getTitle()); - dashboard.setImage(dashboardUpdateMsg.hasImage() ? dashboardUpdateMsg.getImage() : null); - dashboard.setConfiguration(JacksonUtil.toJsonNode(dashboardUpdateMsg.getConfiguration())); - - Set assignedCustomers; - if (dashboardUpdateMsg.hasAssignedCustomers()) { - assignedCustomers = JacksonUtil.fromString(dashboardUpdateMsg.getAssignedCustomers(), new TypeReference<>() {}); - assignedCustomers = filterNonExistingCustomers(tenantId, assignedCustomers); - dashboard.setAssignedCustomers(assignedCustomers); - } - dashboard.setMobileOrder(dashboardUpdateMsg.hasMobileOrder() ? dashboardUpdateMsg.getMobileOrder() : null); - dashboard.setMobileHide(dashboardUpdateMsg.getMobileHide()); - return dashboard; - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessorV2.java deleted file mode 100644 index 10bfe555a6..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessorV2.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.processor.dashboard; - -import org.springframework.context.annotation.Primary; -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.Dashboard; -import org.thingsboard.server.common.data.id.DashboardId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Primary -@Component -@TbCoreComponent -public class DashboardEdgeProcessorV2 extends DashboardEdgeProcessor { - - @Override - protected Dashboard constructDashboardFromUpdateMsg(TenantId tenantId, DashboardId dashboardId, DashboardUpdateMsg dashboardUpdateMsg) { - return JacksonUtil.fromString(dashboardUpdateMsg.getEntity(), Dashboard.class, true); - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardProcessor.java index 4f24d759bb..88837aff54 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardProcessor.java @@ -17,16 +17,12 @@ package org.thingsboard.server.service.edge.rpc.processor.dashboard; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg; -import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.service.edge.rpc.processor.EdgeProcessor; public interface DashboardProcessor extends EdgeProcessor { ListenableFuture processDashboardMsgFromEdge(TenantId tenantId, Edge edge, DashboardUpdateMsg dashboardUpdateMsg); - DownlinkMsg convertDashboardEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/BaseDeviceProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/BaseDeviceProcessor.java index 4b2757e510..05ff217efb 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/BaseDeviceProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/BaseDeviceProcessor.java @@ -18,6 +18,7 @@ package org.thingsboard.server.service.edge.rpc.processor.device; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.util.Pair; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.CustomerId; @@ -40,7 +41,7 @@ public abstract class BaseDeviceProcessor extends BaseEdgeProcessor { boolean deviceNameUpdated = false; deviceCreationLock.lock(); try { - Device device = constructDeviceFromUpdateMsg(tenantId, deviceId, deviceUpdateMsg); + Device device = JacksonUtil.fromString(deviceUpdateMsg.getEntity(), Device.class, true); if (device == null) { throw new RuntimeException("[{" + tenantId + "}] deviceUpdateMsg {" + deviceUpdateMsg + "} cannot be converted to device"); } @@ -78,7 +79,7 @@ public abstract class BaseDeviceProcessor extends BaseEdgeProcessor { } protected void updateDeviceCredentials(TenantId tenantId, DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg) { - DeviceCredentials deviceCredentials = constructDeviceCredentialsFromUpdateMsg(tenantId, deviceCredentialsUpdateMsg); + DeviceCredentials deviceCredentials = JacksonUtil.fromString(deviceCredentialsUpdateMsg.getEntity(), DeviceCredentials.class, true); if (deviceCredentials == null) { throw new RuntimeException("[{" + tenantId + "}] deviceCredentialsUpdateMsg {" + deviceCredentialsUpdateMsg + "} cannot be converted to device credentials"); } @@ -107,10 +108,6 @@ public abstract class BaseDeviceProcessor extends BaseEdgeProcessor { } } - protected abstract Device constructDeviceFromUpdateMsg(TenantId tenantId, DeviceId deviceId, DeviceUpdateMsg deviceUpdateMsg); - protected abstract void setCustomerId(TenantId tenantId, CustomerId customerId, Device device, DeviceUpdateMsg deviceUpdateMsg); - protected abstract DeviceCredentials constructDeviceCredentialsFromUpdateMsg(TenantId tenantId, DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg); - } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java index c4a57e4a74..d9c0d4330f 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java @@ -21,6 +21,7 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.data.util.Pair; +import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; @@ -31,6 +32,7 @@ import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; @@ -47,16 +49,18 @@ import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg; import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueMsgMetadata; -import org.thingsboard.server.service.edge.rpc.constructor.device.DeviceMsgConstructor; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; import java.util.UUID; @Slf4j -public abstract class DeviceEdgeProcessor extends BaseDeviceProcessor implements DeviceProcessor { +@Component +@TbCoreComponent +public class DeviceEdgeProcessor extends BaseDeviceProcessor implements DeviceProcessor { @Override public ListenableFuture processDeviceMsgFromEdge(TenantId tenantId, Edge edge, DeviceUpdateMsg deviceUpdateMsg) { @@ -218,9 +222,8 @@ public abstract class DeviceEdgeProcessor extends BaseDeviceProcessor implements } @Override - public DownlinkMsg convertDeviceEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { + public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { DeviceId deviceId = new DeviceId(edgeEvent.getEntityId()); - var msgConstructor = (DeviceMsgConstructor) edgeCtx.getDeviceMsgConstructorFactory().getMsgConstructorByEdgeVersion(edgeVersion); switch (edgeEvent.getAction()) { case ADDED: case UPDATED: @@ -230,18 +233,18 @@ public abstract class DeviceEdgeProcessor extends BaseDeviceProcessor implements Device device = edgeCtx.getDeviceService().findDeviceById(edgeEvent.getTenantId(), deviceId); if (device != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - DeviceUpdateMsg deviceUpdateMsg = msgConstructor.constructDeviceUpdatedMsg(msgType, device); + DeviceUpdateMsg deviceUpdateMsg = EdgeMsgConstructorUtils.constructDeviceUpdatedMsg(msgType, device); DownlinkMsg.Builder builder = DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addDeviceUpdateMsg(deviceUpdateMsg); DeviceCredentials deviceCredentials = edgeCtx.getDeviceCredentialsService().findDeviceCredentialsByDeviceId(edgeEvent.getTenantId(), deviceId); if (deviceCredentials != null) { - DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg = msgConstructor.constructDeviceCredentialsUpdatedMsg(deviceCredentials); + DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg = EdgeMsgConstructorUtils.constructDeviceCredentialsUpdatedMsg(deviceCredentials); builder.addDeviceCredentialsUpdateMsg(deviceCredentialsUpdateMsg).build(); } if (UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE.equals(msgType)) { DeviceProfile deviceProfile = edgeCtx.getDeviceProfileService().findDeviceProfileById(edgeEvent.getTenantId(), device.getDeviceProfileId()); - builder.addDeviceProfileUpdateMsg(msgConstructor.constructDeviceProfileUpdatedMsg(msgType, deviceProfile)); + builder.addDeviceProfileUpdateMsg(EdgeMsgConstructorUtils.constructDeviceProfileUpdatedMsg(msgType, deviceProfile)); } return builder.build(); } @@ -250,24 +253,35 @@ public abstract class DeviceEdgeProcessor extends BaseDeviceProcessor implements case UNASSIGNED_FROM_EDGE: return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addDeviceUpdateMsg(msgConstructor.constructDeviceDeleteMsg(deviceId)) + .addDeviceUpdateMsg(EdgeMsgConstructorUtils.constructDeviceDeleteMsg(deviceId)) .build(); case CREDENTIALS_UPDATED: DeviceCredentials deviceCredentials = edgeCtx.getDeviceCredentialsService().findDeviceCredentialsByDeviceId(edgeEvent.getTenantId(), deviceId); if (deviceCredentials != null) { return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addDeviceCredentialsUpdateMsg(msgConstructor.constructDeviceCredentialsUpdatedMsg(deviceCredentials)) + .addDeviceCredentialsUpdateMsg(EdgeMsgConstructorUtils.constructDeviceCredentialsUpdatedMsg(deviceCredentials)) .build(); } break; case RPC_CALL: return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addDeviceRpcCallMsg(msgConstructor.constructDeviceRpcCallMsg(edgeEvent.getEntityId(), edgeEvent.getBody())) + .addDeviceRpcCallMsg(EdgeMsgConstructorUtils.constructDeviceRpcCallMsg(edgeEvent.getEntityId(), edgeEvent.getBody())) .build(); } return null; } + @Override + protected void setCustomerId(TenantId tenantId, CustomerId customerId, Device device, DeviceUpdateMsg deviceUpdateMsg) { + CustomerId customerUUID = device.getCustomerId() != null ? device.getCustomerId() : customerId; + device.setCustomerId(customerUUID); + } + + @Override + public EdgeEventType getEdgeEventType() { + return EdgeEventType.DEVICE; + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessorV1.java deleted file mode 100644 index 6756c6c2f8..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessorV1.java +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.processor.device; - -import com.datastax.oss.driver.api.core.uuid.Uuids; -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.DeviceProfileId; -import org.thingsboard.server.common.data.id.OtaPackageId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.security.DeviceCredentials; -import org.thingsboard.server.common.data.security.DeviceCredentialsType; -import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg; -import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; -import org.thingsboard.server.queue.util.TbCoreComponent; - -import java.util.UUID; - -@Component -@TbCoreComponent -public class DeviceEdgeProcessorV1 extends DeviceEdgeProcessor { - - @Override - protected Device constructDeviceFromUpdateMsg(TenantId tenantId, DeviceId deviceId, DeviceUpdateMsg deviceUpdateMsg) { - Device device = new Device(); - device.setTenantId(tenantId); - device.setCreatedTime(Uuids.unixTimestamp(deviceId.getId())); - device.setName(deviceUpdateMsg.getName()); - device.setType(deviceUpdateMsg.getType()); - device.setLabel(deviceUpdateMsg.hasLabel() ? deviceUpdateMsg.getLabel() : null); - device.setAdditionalInfo(deviceUpdateMsg.hasAdditionalInfo() - ? JacksonUtil.toJsonNode(deviceUpdateMsg.getAdditionalInfo()) : null); - - UUID deviceProfileUUID = safeGetUUID(deviceUpdateMsg.getDeviceProfileIdMSB(), deviceUpdateMsg.getDeviceProfileIdLSB()); - device.setDeviceProfileId(deviceProfileUUID != null ? new DeviceProfileId(deviceProfileUUID) : null); - - device.setDeviceDataBytes(deviceUpdateMsg.getDeviceDataBytes().toByteArray()); - - UUID firmwareUUID = safeGetUUID(deviceUpdateMsg.getFirmwareIdMSB(), deviceUpdateMsg.getFirmwareIdLSB()); - device.setFirmwareId(firmwareUUID != null ? new OtaPackageId(firmwareUUID) : null); - UUID softwareUUID = safeGetUUID(deviceUpdateMsg.getSoftwareIdMSB(), deviceUpdateMsg.getSoftwareIdLSB()); - device.setSoftwareId(softwareUUID != null ? new OtaPackageId(softwareUUID) : null); - - return device; - } - - @Override - protected DeviceCredentials constructDeviceCredentialsFromUpdateMsg(TenantId tenantId, DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg) { - DeviceCredentials deviceCredentials = new DeviceCredentials(); - deviceCredentials.setDeviceId(new DeviceId(new UUID(deviceCredentialsUpdateMsg.getDeviceIdMSB(), deviceCredentialsUpdateMsg.getDeviceIdLSB()))); - deviceCredentials.setCredentialsType(DeviceCredentialsType.valueOf(deviceCredentialsUpdateMsg.getCredentialsType())); - deviceCredentials.setCredentialsId(deviceCredentialsUpdateMsg.getCredentialsId()); - deviceCredentials.setCredentialsValue(deviceCredentialsUpdateMsg.hasCredentialsValue() - ? deviceCredentialsUpdateMsg.getCredentialsValue() : null); - return deviceCredentials; - } - - @Override - protected void setCustomerId(TenantId tenantId, CustomerId customerId, Device device, DeviceUpdateMsg deviceUpdateMsg) { - CustomerId customerUUID = safeGetCustomerId(deviceUpdateMsg.getCustomerIdMSB(), deviceUpdateMsg.getCustomerIdLSB()); - device.setCustomerId(customerUUID != null ? customerUUID : customerId); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessorV2.java deleted file mode 100644 index 0ea4f62862..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessorV2.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.processor.device; - -import org.springframework.context.annotation.Primary; -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.security.DeviceCredentials; -import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg; -import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Primary -@Component -@TbCoreComponent -public class DeviceEdgeProcessorV2 extends DeviceEdgeProcessor { - - @Override - protected Device constructDeviceFromUpdateMsg(TenantId tenantId, DeviceId deviceId, DeviceUpdateMsg deviceUpdateMsg) { - return JacksonUtil.fromString(deviceUpdateMsg.getEntity(), Device.class, true); - } - - @Override - protected DeviceCredentials constructDeviceCredentialsFromUpdateMsg(TenantId tenantId, DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg) { - return JacksonUtil.fromString(deviceCredentialsUpdateMsg.getEntity(), DeviceCredentials.class, true); - } - - @Override - protected void setCustomerId(TenantId tenantId, CustomerId customerId, Device device, DeviceUpdateMsg deviceUpdateMsg) { - CustomerId customerUUID = device.getCustomerId() != null ? device.getCustomerId() : customerId; - device.setCustomerId(customerUUID); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceProcessor.java index 087b5f36a7..a7fdbae588 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceProcessor.java @@ -17,14 +17,11 @@ package org.thingsboard.server.service.edge.rpc.processor.device; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg; import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; -import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.service.edge.rpc.processor.EdgeProcessor; public interface DeviceProcessor extends EdgeProcessor { @@ -33,8 +30,6 @@ public interface DeviceProcessor extends EdgeProcessor { ListenableFuture processDeviceCredentialsMsgFromEdge(TenantId tenantId, EdgeId edgeId, DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg); - DownlinkMsg convertDeviceEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion); - ListenableFuture processDeviceRpcCallFromEdge(TenantId tenantId, Edge edge, DeviceRpcCallMsg deviceRpcCallMsg); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/BaseDeviceProfileProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/BaseDeviceProfileProcessor.java index d06618772d..47935da4fd 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/BaseDeviceProfileProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/BaseDeviceProfileProcessor.java @@ -18,6 +18,7 @@ package org.thingsboard.server.service.edge.rpc.processor.device.profile; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.util.Pair; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.DashboardId; @@ -39,7 +40,7 @@ public abstract class BaseDeviceProfileProcessor extends BaseEdgeProcessor { boolean deviceProfileNameUpdated = false; deviceCreationLock.lock(); try { - DeviceProfile deviceProfile = constructDeviceProfileFromUpdateMsg(tenantId, deviceProfileId, deviceProfileUpdateMsg); + DeviceProfile deviceProfile = JacksonUtil.fromString(deviceProfileUpdateMsg.getEntity(), DeviceProfile.class, true); if (deviceProfile == null) { throw new RuntimeException("[{" + tenantId + "}] deviceProfileUpdateMsg {" + deviceProfileUpdateMsg + "} cannot be converted to device profile"); } @@ -81,8 +82,6 @@ public abstract class BaseDeviceProfileProcessor extends BaseEdgeProcessor { return Pair.of(created, deviceProfileNameUpdated); } - protected abstract DeviceProfile constructDeviceProfileFromUpdateMsg(TenantId tenantId, DeviceProfileId deviceProfileId, DeviceProfileUpdateMsg deviceProfileUpdateMsg); - protected abstract void setDefaultRuleChainId(TenantId tenantId, DeviceProfile deviceProfile, RuleChainId ruleChainId); protected abstract void setDefaultEdgeRuleChainId(DeviceProfile deviceProfile, RuleChainId ruleChainId, DeviceProfileUpdateMsg deviceProfileUpdateMsg); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessor.java index a0a699d340..eef994be93 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessor.java @@ -19,6 +19,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.data.util.Pair; +import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EdgeUtils; @@ -26,21 +27,25 @@ import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; +import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.service.edge.rpc.constructor.device.DeviceMsgConstructor; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; import java.util.UUID; @Slf4j -public abstract class DeviceProfileEdgeProcessor extends BaseDeviceProfileProcessor implements DeviceProfileProcessor { +@Component +@TbCoreComponent +public class DeviceProfileEdgeProcessor extends BaseDeviceProfileProcessor implements DeviceProfileProcessor { @Override public ListenableFuture processDeviceProfileMsgFromEdge(TenantId tenantId, Edge edge, DeviceProfileUpdateMsg deviceProfileUpdateMsg) { @@ -49,16 +54,13 @@ public abstract class DeviceProfileEdgeProcessor extends BaseDeviceProfileProces try { edgeSynchronizationManager.getEdgeId().set(edge.getId()); - switch (deviceProfileUpdateMsg.getMsgType()) { - case ENTITY_CREATED_RPC_MESSAGE: - case ENTITY_UPDATED_RPC_MESSAGE: + return switch (deviceProfileUpdateMsg.getMsgType()) { + case ENTITY_CREATED_RPC_MESSAGE, ENTITY_UPDATED_RPC_MESSAGE -> { saveOrUpdateDeviceProfile(tenantId, deviceProfileId, deviceProfileUpdateMsg, edge); - return Futures.immediateFuture(null); - case ENTITY_DELETED_RPC_MESSAGE: - case UNRECOGNIZED: - default: - return handleUnsupportedMsgType(deviceProfileUpdateMsg.getMsgType()); - } + yield Futures.immediateFuture(null); + } + default -> handleUnsupportedMsgType(deviceProfileUpdateMsg.getMsgType()); + }; } catch (DataValidationException e) { log.warn("[{}] Failed to process DeviceProfileUpdateMsg from Edge [{}]", tenantId, deviceProfileUpdateMsg, e); return Futures.immediateFailedFuture(e); @@ -92,15 +94,14 @@ public abstract class DeviceProfileEdgeProcessor extends BaseDeviceProfileProces } @Override - public DownlinkMsg convertDeviceProfileEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { + public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { DeviceProfileId deviceProfileId = new DeviceProfileId(edgeEvent.getEntityId()); - var msgConstructor = (DeviceMsgConstructor) edgeCtx.getDeviceMsgConstructorFactory().getMsgConstructorByEdgeVersion(edgeVersion); switch (edgeEvent.getAction()) { case ADDED, UPDATED -> { DeviceProfile deviceProfile = edgeCtx.getDeviceProfileService().findDeviceProfileById(edgeEvent.getTenantId(), deviceProfileId); if (deviceProfile != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - DeviceProfileUpdateMsg deviceProfileUpdateMsg = msgConstructor.constructDeviceProfileUpdatedMsg(msgType, deviceProfile); + DeviceProfileUpdateMsg deviceProfileUpdateMsg = EdgeMsgConstructorUtils.constructDeviceProfileUpdatedMsg(msgType, deviceProfile); return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addDeviceProfileUpdateMsg(deviceProfileUpdateMsg) @@ -108,7 +109,7 @@ public abstract class DeviceProfileEdgeProcessor extends BaseDeviceProfileProces } } case DELETED -> { - DeviceProfileUpdateMsg deviceProfileUpdateMsg = msgConstructor.constructDeviceProfileDeleteMsg(deviceProfileId); + DeviceProfileUpdateMsg deviceProfileUpdateMsg = EdgeMsgConstructorUtils.constructDeviceProfileDeleteMsg(deviceProfileId); return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addDeviceProfileUpdateMsg(deviceProfileUpdateMsg) @@ -118,4 +119,26 @@ public abstract class DeviceProfileEdgeProcessor extends BaseDeviceProfileProces return null; } + @Override + protected void setDefaultRuleChainId(TenantId tenantId, DeviceProfile deviceProfile, RuleChainId ruleChainId) { + deviceProfile.setDefaultRuleChainId(ruleChainId); + } + + @Override + protected void setDefaultEdgeRuleChainId(DeviceProfile deviceProfile, RuleChainId ruleChainId, DeviceProfileUpdateMsg deviceProfileUpdateMsg) { + UUID defaultEdgeRuleChainUUID = ruleChainId != null ? ruleChainId.getId() : null; + deviceProfile.setDefaultEdgeRuleChainId(defaultEdgeRuleChainUUID != null ? new RuleChainId(defaultEdgeRuleChainUUID) : null); + } + + @Override + protected void setDefaultDashboardId(TenantId tenantId, DashboardId dashboardId, DeviceProfile deviceProfile, DeviceProfileUpdateMsg deviceProfileUpdateMsg) { + UUID defaultDashboardUUID = deviceProfile.getDefaultDashboardId() != null ? deviceProfile.getDefaultDashboardId().getId() : (dashboardId != null ? dashboardId.getId() : null); + deviceProfile.setDefaultDashboardId(defaultDashboardUUID != null ? new DashboardId(defaultDashboardUUID) : null); + } + + @Override + public EdgeEventType getEdgeEventType() { + return EdgeEventType.DEVICE_PROFILE; + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessorFactory.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessorFactory.java deleted file mode 100644 index a69fba1fb5..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessorFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.processor.device.profile; - -import org.springframework.stereotype.Component; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessorFactory; - -@Component -@TbCoreComponent -public class DeviceProfileEdgeProcessorFactory extends BaseEdgeProcessorFactory { - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessorV1.java deleted file mode 100644 index a86afcfb73..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessorV1.java +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.processor.device.profile; - -import com.datastax.oss.driver.api.core.uuid.Uuids; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.DeviceProfileProvisionType; -import org.thingsboard.server.common.data.DeviceProfileType; -import org.thingsboard.server.common.data.DeviceTransportType; -import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.data.id.DashboardId; -import org.thingsboard.server.common.data.id.DeviceProfileId; -import org.thingsboard.server.common.data.id.OtaPackageId; -import org.thingsboard.server.common.data.id.RuleChainId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg; -import org.thingsboard.server.queue.util.TbCoreComponent; - -import java.nio.charset.StandardCharsets; -import java.util.UUID; - -@Component -@TbCoreComponent -public class DeviceProfileEdgeProcessorV1 extends DeviceProfileEdgeProcessor { - - @Override - protected DeviceProfile constructDeviceProfileFromUpdateMsg(TenantId tenantId, DeviceProfileId deviceProfileId, DeviceProfileUpdateMsg deviceProfileUpdateMsg) { - DeviceProfile deviceProfile = new DeviceProfile(); - deviceProfile.setTenantId(tenantId); - deviceProfile.setCreatedTime(Uuids.unixTimestamp(deviceProfileId.getId())); - deviceProfile.setName(deviceProfileUpdateMsg.getName()); - deviceProfile.setDefault(deviceProfileUpdateMsg.getDefault()); - deviceProfile.setDescription(deviceProfileUpdateMsg.hasDescription() ? deviceProfileUpdateMsg.getDescription() : null); - deviceProfile.setType(DeviceProfileType.valueOf(deviceProfileUpdateMsg.getType())); - deviceProfile.setTransportType(deviceProfileUpdateMsg.hasTransportType() - ? DeviceTransportType.valueOf(deviceProfileUpdateMsg.getTransportType()) : DeviceTransportType.DEFAULT); - deviceProfile.setImage(deviceProfileUpdateMsg.hasImage() - ? new String(deviceProfileUpdateMsg.getImage().toByteArray(), StandardCharsets.UTF_8) : null); - deviceProfile.setProvisionType(deviceProfileUpdateMsg.hasProvisionType() - ? DeviceProfileProvisionType.valueOf(deviceProfileUpdateMsg.getProvisionType()) : DeviceProfileProvisionType.DISABLED); - deviceProfile.setProvisionDeviceKey(deviceProfileUpdateMsg.hasProvisionDeviceKey() - ? deviceProfileUpdateMsg.getProvisionDeviceKey() : null); - deviceProfile.setDefaultQueueName(deviceProfileUpdateMsg.getDefaultQueueName()); - - deviceProfile.setProfileDataBytes(deviceProfileUpdateMsg.getProfileDataBytes().toByteArray()); - - String defaultQueueName = StringUtils.isNotBlank(deviceProfileUpdateMsg.getDefaultQueueName()) - ? deviceProfileUpdateMsg.getDefaultQueueName() : null; - deviceProfile.setDefaultQueueName(defaultQueueName); - - UUID firmwareUUID = safeGetUUID(deviceProfileUpdateMsg.getFirmwareIdMSB(), deviceProfileUpdateMsg.getFirmwareIdLSB()); - deviceProfile.setFirmwareId(firmwareUUID != null ? new OtaPackageId(firmwareUUID) : null); - - UUID softwareUUID = safeGetUUID(deviceProfileUpdateMsg.getSoftwareIdMSB(), deviceProfileUpdateMsg.getSoftwareIdLSB()); - deviceProfile.setSoftwareId(softwareUUID != null ? new OtaPackageId(softwareUUID) : null); - return deviceProfile; - } - - @Override - protected void setDefaultRuleChainId(TenantId tenantId, DeviceProfile deviceProfile, RuleChainId ruleChainId) { - deviceProfile.setDefaultRuleChainId(ruleChainId); - } - - @Override - protected void setDefaultEdgeRuleChainId(DeviceProfile deviceProfile, RuleChainId ruleChainId, DeviceProfileUpdateMsg deviceProfileUpdateMsg) { - UUID defaultEdgeRuleChainUUID = safeGetUUID(deviceProfileUpdateMsg.getDefaultRuleChainIdMSB(), deviceProfileUpdateMsg.getDefaultRuleChainIdLSB()); - deviceProfile.setDefaultEdgeRuleChainId(defaultEdgeRuleChainUUID != null ? new RuleChainId(defaultEdgeRuleChainUUID) : ruleChainId); - } - - @Override - protected void setDefaultDashboardId(TenantId tenantId, DashboardId dashboardId, DeviceProfile deviceProfile, DeviceProfileUpdateMsg deviceProfileUpdateMsg) { - UUID defaultDashboardUUID = safeGetUUID(deviceProfileUpdateMsg.getDefaultDashboardIdMSB(), deviceProfileUpdateMsg.getDefaultDashboardIdLSB()); - deviceProfile.setDefaultDashboardId(defaultDashboardUUID != null ? new DashboardId(defaultDashboardUUID) : dashboardId); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessorV2.java deleted file mode 100644 index 15c07fdaa7..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessorV2.java +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.processor.device.profile; - -import org.springframework.context.annotation.Primary; -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.id.DashboardId; -import org.thingsboard.server.common.data.id.DeviceProfileId; -import org.thingsboard.server.common.data.id.RuleChainId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg; -import org.thingsboard.server.queue.util.TbCoreComponent; - -import java.util.UUID; - -@Primary -@Component -@TbCoreComponent -public class DeviceProfileEdgeProcessorV2 extends DeviceProfileEdgeProcessor { - - @Override - protected DeviceProfile constructDeviceProfileFromUpdateMsg(TenantId tenantId, DeviceProfileId deviceProfileId, DeviceProfileUpdateMsg deviceProfileUpdateMsg) { - return JacksonUtil.fromString(deviceProfileUpdateMsg.getEntity(), DeviceProfile.class, true); - } - - @Override - protected void setDefaultRuleChainId(TenantId tenantId, DeviceProfile deviceProfile, RuleChainId ruleChainId) { - deviceProfile.setDefaultRuleChainId(ruleChainId); - } - - @Override - protected void setDefaultEdgeRuleChainId(DeviceProfile deviceProfile, RuleChainId ruleChainId, DeviceProfileUpdateMsg deviceProfileUpdateMsg) { - UUID defaultEdgeRuleChainUUID = ruleChainId != null ? ruleChainId.getId() : null; - deviceProfile.setDefaultEdgeRuleChainId(defaultEdgeRuleChainUUID != null ? new RuleChainId(defaultEdgeRuleChainUUID) : null); - } - - @Override - protected void setDefaultDashboardId(TenantId tenantId, DashboardId dashboardId, DeviceProfile deviceProfile, DeviceProfileUpdateMsg deviceProfileUpdateMsg) { - UUID defaultDashboardUUID = deviceProfile.getDefaultDashboardId() != null ? deviceProfile.getDefaultDashboardId().getId() : (dashboardId != null ? dashboardId.getId() : null); - deviceProfile.setDefaultDashboardId(defaultDashboardUUID != null ? new DashboardId(defaultDashboardUUID) : null); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileProcessor.java index 817d35879d..77bf9500c5 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileProcessor.java @@ -17,17 +17,12 @@ package org.thingsboard.server.service.edge.rpc.processor.device.profile; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg; -import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.service.edge.rpc.processor.EdgeProcessor; public interface DeviceProfileProcessor extends EdgeProcessor { ListenableFuture processDeviceProfileMsgFromEdge(TenantId tenantId, Edge edge, DeviceProfileUpdateMsg deviceProfileUpdateMsg); - DownlinkMsg convertDeviceProfileEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion); - } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/edge/EdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/edge/EdgeEntityProcessor.java similarity index 92% rename from application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/edge/EdgeProcessor.java rename to application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/edge/EdgeEntityProcessor.java index 024147507a..7e969d5db4 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/edge/EdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/edge/EdgeEntityProcessor.java @@ -32,9 +32,9 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.EdgeConfiguration; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; import java.util.ArrayList; @@ -44,26 +44,10 @@ import java.util.UUID; @Slf4j @Component @TbCoreComponent -public class EdgeProcessor extends BaseEdgeProcessor { +public class EdgeEntityProcessor extends BaseEdgeProcessor { - public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { - EdgeId edgeId = new EdgeId(edgeEvent.getEntityId()); - switch (edgeEvent.getAction()) { - case ASSIGNED_TO_CUSTOMER, UNASSIGNED_FROM_CUSTOMER -> { - Edge edge = edgeCtx.getEdgeService().findEdgeById(edgeEvent.getTenantId(), edgeId); - if (edge != null) { - EdgeConfiguration edgeConfigMsg = edgeCtx.getEdgeMsgConstructor().constructEdgeConfiguration(edge); - return DownlinkMsg.newBuilder() - .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .setEdgeConfiguration(edgeConfigMsg) - .build(); - } - } - } - return null; - } - - public ListenableFuture processEdgeNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { + @Override + public ListenableFuture processEntityNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { try { EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); @@ -112,4 +96,26 @@ public class EdgeProcessor extends BaseEdgeProcessor { } } + @Override + public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { + EdgeId edgeId = new EdgeId(edgeEvent.getEntityId()); + switch (edgeEvent.getAction()) { + case ASSIGNED_TO_CUSTOMER, UNASSIGNED_FROM_CUSTOMER -> { + Edge edge = edgeCtx.getEdgeService().findEdgeById(edgeEvent.getTenantId(), edgeId); + if (edge != null) { + return DownlinkMsg.newBuilder() + .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) + .setEdgeConfiguration(EdgeMsgConstructorUtils.constructEdgeConfiguration(edge)) + .build(); + } + } + } + return null; + } + + @Override + public EdgeEventType getEdgeEventType() { + return EdgeEventType.EDGE; + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/BaseEntityViewProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/BaseEntityViewProcessor.java index 757f606f69..2ded93c253 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/BaseEntityViewProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/BaseEntityViewProcessor.java @@ -18,6 +18,7 @@ package org.thingsboard.server.service.edge.rpc.processor.entityview; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.util.Pair; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.CustomerId; @@ -36,7 +37,7 @@ public abstract class BaseEntityViewProcessor extends BaseEdgeProcessor { protected Pair saveOrUpdateEntityView(TenantId tenantId, EntityViewId entityViewId, EntityViewUpdateMsg entityViewUpdateMsg) { boolean created = false; boolean entityViewNameUpdated = false; - EntityView entityView = constructEntityViewFromUpdateMsg(tenantId, entityViewId, entityViewUpdateMsg); + EntityView entityView = JacksonUtil.fromString(entityViewUpdateMsg.getEntity(), EntityView.class, true); if (entityView == null) { throw new RuntimeException("[{" + tenantId + "}] entityViewUpdateMsg {" + entityViewUpdateMsg + "} cannot be converted to entity view"); } @@ -66,8 +67,6 @@ public abstract class BaseEntityViewProcessor extends BaseEdgeProcessor { return Pair.of(created, entityViewNameUpdated); } - protected abstract EntityView constructEntityViewFromUpdateMsg(TenantId tenantId, EntityViewId entityViewId, EntityViewUpdateMsg entityViewUpdateMsg); - protected abstract void setCustomerId(TenantId tenantId, CustomerId customerId, EntityView entityView, EntityViewUpdateMsg entityViewUpdateMsg); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewEdgeProcessor.java index aa105652d5..620e9c9c34 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewEdgeProcessor.java @@ -18,8 +18,8 @@ package org.thingsboard.server.service.edge.rpc.processor.entityview; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.util.Pair; +import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EntityView; @@ -27,25 +27,24 @@ import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.service.edge.rpc.constructor.entityview.EntityViewMsgConstructor; -import org.thingsboard.server.service.edge.rpc.constructor.entityview.EntityViewMsgConstructorFactory; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; import java.util.UUID; @Slf4j -public abstract class EntityViewEdgeProcessor extends BaseEntityViewProcessor implements EntityViewProcessor { - - @Autowired - private EntityViewMsgConstructorFactory entityViewMsgConstructorFactory; +@Component +@TbCoreComponent +public class EntityViewEdgeProcessor extends BaseEntityViewProcessor implements EntityViewProcessor { @Override public ListenableFuture processEntityViewMsgFromEdge(TenantId tenantId, Edge edge, EntityViewUpdateMsg entityViewUpdateMsg) { @@ -106,15 +105,15 @@ public abstract class EntityViewEdgeProcessor extends BaseEntityViewProcessor im } } - public DownlinkMsg convertEntityViewEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { + @Override + public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { EntityViewId entityViewId = new EntityViewId(edgeEvent.getEntityId()); - var msgConstructor = (EntityViewMsgConstructor) entityViewMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion); switch (edgeEvent.getAction()) { case ADDED, UPDATED, ASSIGNED_TO_EDGE, ASSIGNED_TO_CUSTOMER, UNASSIGNED_FROM_CUSTOMER -> { EntityView entityView = edgeCtx.getEntityViewService().findEntityViewById(edgeEvent.getTenantId(), entityViewId); if (entityView != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - EntityViewUpdateMsg entityViewUpdateMsg = msgConstructor.constructEntityViewUpdatedMsg(msgType, entityView); + EntityViewUpdateMsg entityViewUpdateMsg = EdgeMsgConstructorUtils.constructEntityViewUpdatedMsg(msgType, entityView); return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addEntityViewUpdateMsg(entityViewUpdateMsg) @@ -122,7 +121,7 @@ public abstract class EntityViewEdgeProcessor extends BaseEntityViewProcessor im } } case DELETED, UNASSIGNED_FROM_EDGE -> { - EntityViewUpdateMsg entityViewUpdateMsg = msgConstructor.constructEntityViewDeleteMsg(entityViewId); + EntityViewUpdateMsg entityViewUpdateMsg = EdgeMsgConstructorUtils.constructEntityViewDeleteMsg(entityViewId); return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addEntityViewUpdateMsg(entityViewUpdateMsg) @@ -132,4 +131,15 @@ public abstract class EntityViewEdgeProcessor extends BaseEntityViewProcessor im return null; } + @Override + protected void setCustomerId(TenantId tenantId, CustomerId customerId, EntityView entityView, EntityViewUpdateMsg entityViewUpdateMsg) { + CustomerId customerUUID = entityView.getCustomerId() != null ? entityView.getCustomerId() : customerId; + entityView.setCustomerId(customerUUID); + } + + @Override + public EdgeEventType getEdgeEventType() { + return EdgeEventType.ENTITY_VIEW; + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewProcessor.java index 061cd89bf0..b0eaf1d60e 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewProcessor.java @@ -17,10 +17,7 @@ package org.thingsboard.server.service.edge.rpc.processor.entityview; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg; import org.thingsboard.server.service.edge.rpc.processor.EdgeProcessor; @@ -28,6 +25,4 @@ public interface EntityViewProcessor extends EdgeProcessor { ListenableFuture processEntityViewMsgFromEdge(TenantId tenantId, Edge edge, EntityViewUpdateMsg entityViewUpdateMsg); - DownlinkMsg convertEntityViewEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion); - } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewProcessorFactory.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewProcessorFactory.java deleted file mode 100644 index d6a3f0c250..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewProcessorFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.processor.entityview; - -import org.springframework.stereotype.Component; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessorFactory; - -@Component -@TbCoreComponent -public class EntityViewProcessorFactory extends BaseEdgeProcessorFactory { - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewProcessorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewProcessorV1.java deleted file mode 100644 index acb8877c03..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewProcessorV1.java +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.processor.entityview; - -import com.datastax.oss.driver.api.core.uuid.Uuids; -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.EntityView; -import org.thingsboard.server.common.data.id.AssetId; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.EntityViewId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.gen.edge.v1.EdgeEntityType; -import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg; -import org.thingsboard.server.queue.util.TbCoreComponent; - -import java.util.UUID; - -@Component -@TbCoreComponent -public class EntityViewProcessorV1 extends EntityViewEdgeProcessor { - - @Override - protected EntityView constructEntityViewFromUpdateMsg(TenantId tenantId, EntityViewId entityViewId, EntityViewUpdateMsg entityViewUpdateMsg) { - EntityView entityView = new EntityView(); - entityView.setTenantId(tenantId); - entityView.setCreatedTime(Uuids.unixTimestamp(entityViewId.getId())); - entityView.setName(entityViewUpdateMsg.getName()); - entityView.setType(entityViewUpdateMsg.getType()); - - entityView.setAdditionalInfo(entityViewUpdateMsg.hasAdditionalInfo() ? - JacksonUtil.toJsonNode(entityViewUpdateMsg.getAdditionalInfo()) : null); - - CustomerId customerId = safeGetCustomerId(entityViewUpdateMsg.getCustomerIdMSB(), entityViewUpdateMsg.getCustomerIdLSB()); - entityView.setCustomerId(customerId); - - UUID entityIdUUID = safeGetUUID(entityViewUpdateMsg.getEntityIdMSB(), entityViewUpdateMsg.getEntityIdLSB()); - if (EdgeEntityType.DEVICE.equals(entityViewUpdateMsg.getEntityType())) { - entityView.setEntityId(entityIdUUID != null ? new DeviceId(entityIdUUID) : null); - } else if (EdgeEntityType.ASSET.equals(entityViewUpdateMsg.getEntityType())) { - entityView.setEntityId(entityIdUUID != null ? new AssetId(entityIdUUID) : null); - } - return entityView; - } - - @Override - protected void setCustomerId(TenantId tenantId, CustomerId customerId, EntityView entityView, EntityViewUpdateMsg entityViewUpdateMsg) { - CustomerId customerUUID = safeGetCustomerId(entityViewUpdateMsg.getCustomerIdMSB(), entityViewUpdateMsg.getCustomerIdLSB()); - entityView.setCustomerId(customerUUID != null ? customerUUID : customerId); - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewProcessorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewProcessorV2.java deleted file mode 100644 index fa4177552e..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewProcessorV2.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.processor.entityview; - -import org.springframework.context.annotation.Primary; -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.EntityView; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.EntityViewId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Primary -@Component -@TbCoreComponent -public class EntityViewProcessorV2 extends EntityViewEdgeProcessor { - - @Override - protected EntityView constructEntityViewFromUpdateMsg(TenantId tenantId, EntityViewId entityViewId, EntityViewUpdateMsg entityViewUpdateMsg) { - return JacksonUtil.fromString(entityViewUpdateMsg.getEntity(), EntityView.class, true); - } - - @Override - protected void setCustomerId(TenantId tenantId, CustomerId customerId, EntityView entityView, EntityViewUpdateMsg entityViewUpdateMsg) { - CustomerId customerUUID = entityView.getCustomerId() != null ? entityView.getCustomerId() : customerId; - entityView.setCustomerId(customerUUID); - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/notification/NotificationEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/notification/NotificationEdgeProcessor.java deleted file mode 100644 index 67aa1dece2..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/notification/NotificationEdgeProcessor.java +++ /dev/null @@ -1,136 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.processor.notification; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.EdgeUtils; -import org.thingsboard.server.common.data.edge.EdgeEvent; -import org.thingsboard.server.common.data.id.NotificationRuleId; -import org.thingsboard.server.common.data.id.NotificationTargetId; -import org.thingsboard.server.common.data.id.NotificationTemplateId; -import org.thingsboard.server.common.data.notification.rule.NotificationRule; -import org.thingsboard.server.common.data.notification.targets.NotificationTarget; -import org.thingsboard.server.common.data.notification.template.NotificationTemplate; -import org.thingsboard.server.dao.notification.NotificationRuleService; -import org.thingsboard.server.dao.notification.NotificationTargetService; -import org.thingsboard.server.dao.notification.NotificationTemplateService; -import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.NotificationRuleUpdateMsg; -import org.thingsboard.server.gen.edge.v1.NotificationTargetUpdateMsg; -import org.thingsboard.server.gen.edge.v1.NotificationTemplateUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.constructor.notification.NotificationMsgConstructor; -import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; - -@Slf4j -@Component -@TbCoreComponent -public class NotificationEdgeProcessor extends BaseEdgeProcessor { - - @Autowired - protected NotificationRuleService notificationRuleService; - - @Autowired - private NotificationTargetService notificationTargetService; - - @Autowired - private NotificationTemplateService notificationTemplateService; - - @Autowired - private NotificationMsgConstructor notificationMsgConstructor; - - public DownlinkMsg convertNotificationRuleToDownlink(EdgeEvent edgeEvent) { - NotificationRuleId notificationRuleId = new NotificationRuleId(edgeEvent.getEntityId()); - DownlinkMsg downlinkMsg = null; - switch (edgeEvent.getAction()) { - case ADDED, UPDATED -> { - NotificationRule notificationRule = notificationRuleService.findNotificationRuleById(edgeEvent.getTenantId(), notificationRuleId); - if (notificationRule != null) { - UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - NotificationRuleUpdateMsg notificationRuleUpdateMsg = notificationMsgConstructor.constructNotificationRuleUpdateMsg(msgType, notificationRule); - downlinkMsg = DownlinkMsg.newBuilder() - .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addNotificationRuleUpdateMsg(notificationRuleUpdateMsg) - .build(); - } - } - case DELETED -> { - NotificationRuleUpdateMsg notificationRuleUpdateMsg = notificationMsgConstructor.constructNotificationRuleDeleteMsg(notificationRuleId); - downlinkMsg = DownlinkMsg.newBuilder() - .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addNotificationRuleUpdateMsg(notificationRuleUpdateMsg) - .build(); - } - } - return downlinkMsg; - } - - public DownlinkMsg convertNotificationTargetToDownlink(EdgeEvent edgeEvent) { - NotificationTargetId notificationTargetId = new NotificationTargetId(edgeEvent.getEntityId()); - DownlinkMsg downlinkMsg = null; - switch (edgeEvent.getAction()) { - case ADDED, UPDATED -> { - NotificationTarget notificationTarget = notificationTargetService.findNotificationTargetById(edgeEvent.getTenantId(), notificationTargetId); - if (notificationTarget != null) { - UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - NotificationTargetUpdateMsg notificationTargetUpdateMsg = notificationMsgConstructor.constructNotificationTargetUpdateMsg(msgType, notificationTarget); - downlinkMsg = DownlinkMsg.newBuilder() - .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addNotificationTargetUpdateMsg(notificationTargetUpdateMsg) - .build(); - } - } - case DELETED -> { - NotificationTargetUpdateMsg notificationTargetUpdateMsg = notificationMsgConstructor.constructNotificationTargetDeleteMsg(notificationTargetId); - downlinkMsg = DownlinkMsg.newBuilder() - .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addNotificationTargetUpdateMsg(notificationTargetUpdateMsg) - .build(); - } - } - return downlinkMsg; - } - - public DownlinkMsg convertNotificationTemplateToDownlink(EdgeEvent edgeEvent) { - NotificationTemplateId notificationTemplateId = new NotificationTemplateId(edgeEvent.getEntityId()); - DownlinkMsg downlinkMsg = null; - switch (edgeEvent.getAction()) { - case ADDED, UPDATED -> { - NotificationTemplate notificationTemplate = notificationTemplateService.findNotificationTemplateById(edgeEvent.getTenantId(), notificationTemplateId); - if (notificationTemplate != null) { - UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - NotificationTemplateUpdateMsg notificationTemplateUpdateMsg = notificationMsgConstructor.constructNotificationTemplateUpdateMsg(msgType, notificationTemplate); - downlinkMsg = DownlinkMsg.newBuilder() - .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addNotificationTemplateUpdateMsg(notificationTemplateUpdateMsg) - .build(); - } - } - case DELETED -> { - NotificationTemplateUpdateMsg notificationTemplateUpdateMsg = notificationMsgConstructor.constructNotificationTemplateDeleteMsg(notificationTemplateId); - downlinkMsg = DownlinkMsg.newBuilder() - .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addNotificationTemplateUpdateMsg(notificationTemplateUpdateMsg) - .build(); - } - } - return downlinkMsg; - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/notification/NotificationRuleEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/notification/NotificationRuleEdgeProcessor.java new file mode 100644 index 0000000000..0322c17b74 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/notification/NotificationRuleEdgeProcessor.java @@ -0,0 +1,74 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc.processor.notification; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.EdgeUtils; +import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventType; +import org.thingsboard.server.common.data.id.NotificationRuleId; +import org.thingsboard.server.common.data.notification.rule.NotificationRule; +import org.thingsboard.server.dao.notification.NotificationRuleService; +import org.thingsboard.server.gen.edge.v1.DownlinkMsg; +import org.thingsboard.server.gen.edge.v1.NotificationRuleUpdateMsg; +import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; +import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; + +@Slf4j +@Component +@TbCoreComponent +public class NotificationRuleEdgeProcessor extends BaseEdgeProcessor { + + @Autowired + private NotificationRuleService notificationRuleService; + + @Override + public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { + NotificationRuleId notificationRuleId = new NotificationRuleId(edgeEvent.getEntityId()); + DownlinkMsg downlinkMsg = null; + switch (edgeEvent.getAction()) { + case ADDED, UPDATED -> { + NotificationRule notificationRule = notificationRuleService.findNotificationRuleById(edgeEvent.getTenantId(), notificationRuleId); + if (notificationRule != null) { + UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); + NotificationRuleUpdateMsg notificationRuleUpdateMsg = EdgeMsgConstructorUtils.constructNotificationRuleUpdateMsg(msgType, notificationRule); + downlinkMsg = DownlinkMsg.newBuilder() + .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) + .addNotificationRuleUpdateMsg(notificationRuleUpdateMsg) + .build(); + } + } + case DELETED -> { + NotificationRuleUpdateMsg notificationRuleUpdateMsg = EdgeMsgConstructorUtils.constructNotificationRuleDeleteMsg(notificationRuleId); + downlinkMsg = DownlinkMsg.newBuilder() + .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) + .addNotificationRuleUpdateMsg(notificationRuleUpdateMsg) + .build(); + } + } + return downlinkMsg; + } + + @Override + public EdgeEventType getEdgeEventType() { + return EdgeEventType.NOTIFICATION_RULE; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/notification/NotificationTargetEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/notification/NotificationTargetEdgeProcessor.java new file mode 100644 index 0000000000..a9a6044812 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/notification/NotificationTargetEdgeProcessor.java @@ -0,0 +1,74 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc.processor.notification; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.EdgeUtils; +import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventType; +import org.thingsboard.server.common.data.id.NotificationTargetId; +import org.thingsboard.server.common.data.notification.targets.NotificationTarget; +import org.thingsboard.server.dao.notification.NotificationTargetService; +import org.thingsboard.server.gen.edge.v1.DownlinkMsg; +import org.thingsboard.server.gen.edge.v1.NotificationTargetUpdateMsg; +import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; +import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; + +@Slf4j +@Component +@TbCoreComponent +public class NotificationTargetEdgeProcessor extends BaseEdgeProcessor { + + @Autowired + private NotificationTargetService notificationTargetService; + + @Override + public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { + NotificationTargetId notificationTargetId = new NotificationTargetId(edgeEvent.getEntityId()); + DownlinkMsg downlinkMsg = null; + switch (edgeEvent.getAction()) { + case ADDED, UPDATED -> { + NotificationTarget notificationTarget = notificationTargetService.findNotificationTargetById(edgeEvent.getTenantId(), notificationTargetId); + if (notificationTarget != null) { + UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); + NotificationTargetUpdateMsg notificationTargetUpdateMsg = EdgeMsgConstructorUtils.constructNotificationTargetUpdateMsg(msgType, notificationTarget); + downlinkMsg = DownlinkMsg.newBuilder() + .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) + .addNotificationTargetUpdateMsg(notificationTargetUpdateMsg) + .build(); + } + } + case DELETED -> { + NotificationTargetUpdateMsg notificationTargetUpdateMsg = EdgeMsgConstructorUtils.constructNotificationTargetDeleteMsg(notificationTargetId); + downlinkMsg = DownlinkMsg.newBuilder() + .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) + .addNotificationTargetUpdateMsg(notificationTargetUpdateMsg) + .build(); + } + } + return downlinkMsg; + } + + @Override + public EdgeEventType getEdgeEventType() { + return EdgeEventType.NOTIFICATION_TARGET; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/notification/NotificationTemplateEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/notification/NotificationTemplateEdgeProcessor.java new file mode 100644 index 0000000000..589db4912b --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/notification/NotificationTemplateEdgeProcessor.java @@ -0,0 +1,74 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc.processor.notification; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.EdgeUtils; +import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventType; +import org.thingsboard.server.common.data.id.NotificationTemplateId; +import org.thingsboard.server.common.data.notification.template.NotificationTemplate; +import org.thingsboard.server.dao.notification.NotificationTemplateService; +import org.thingsboard.server.gen.edge.v1.DownlinkMsg; +import org.thingsboard.server.gen.edge.v1.NotificationTemplateUpdateMsg; +import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; +import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; + +@Slf4j +@Component +@TbCoreComponent +public class NotificationTemplateEdgeProcessor extends BaseEdgeProcessor { + + @Autowired + private NotificationTemplateService notificationTemplateService; + + @Override + public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { + NotificationTemplateId notificationTemplateId = new NotificationTemplateId(edgeEvent.getEntityId()); + DownlinkMsg downlinkMsg = null; + switch (edgeEvent.getAction()) { + case ADDED, UPDATED -> { + NotificationTemplate notificationTemplate = notificationTemplateService.findNotificationTemplateById(edgeEvent.getTenantId(), notificationTemplateId); + if (notificationTemplate != null) { + UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); + NotificationTemplateUpdateMsg notificationTemplateUpdateMsg = EdgeMsgConstructorUtils.constructNotificationTemplateUpdateMsg(msgType, notificationTemplate); + downlinkMsg = DownlinkMsg.newBuilder() + .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) + .addNotificationTemplateUpdateMsg(notificationTemplateUpdateMsg) + .build(); + } + } + case DELETED -> { + NotificationTemplateUpdateMsg notificationTemplateUpdateMsg = EdgeMsgConstructorUtils.constructNotificationTemplateDeleteMsg(notificationTemplateId); + downlinkMsg = DownlinkMsg.newBuilder() + .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) + .addNotificationTemplateUpdateMsg(notificationTemplateUpdateMsg) + .build(); + } + } + return downlinkMsg; + } + + @Override + public EdgeEventType getEdgeEventType() { + return EdgeEventType.NOTIFICATION_TEMPLATE; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/OAuth2EdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/DomainEdgeProcessor.java similarity index 54% rename from application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/OAuth2EdgeProcessor.java rename to application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/DomainEdgeProcessor.java index 9f4af7d0dc..4eb3e4001d 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/OAuth2EdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/DomainEdgeProcessor.java @@ -21,56 +21,48 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.domain.DomainInfo; import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.DomainId; -import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.dao.domain.DomainService; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.OAuth2ClientUpdateMsg; import org.thingsboard.server.gen.edge.v1.OAuth2DomainUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.constructor.oauth2.OAuth2MsgConstructor; +import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.utils.EdgeVersionUtils; @Slf4j @Component @TbCoreComponent -public class OAuth2EdgeProcessor extends BaseEdgeProcessor { +public class DomainEdgeProcessor extends BaseEdgeProcessor { @Autowired private DomainService domainService; - @Autowired - private OAuth2MsgConstructor oAuth2MsgConstructor; - - public DownlinkMsg convertOAuth2DomainEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { - if (EdgeVersionUtils.isEdgeVersionOlderThan(edgeVersion, EdgeVersion.V_3_8_0)) { - return null; - } + @Override + public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { DomainId domainId = new DomainId(edgeEvent.getEntityId()); - switch (edgeEvent.getAction()) { case ADDED, UPDATED -> { DomainInfo domainInfo = domainService.findDomainInfoById(edgeEvent.getTenantId(), domainId); if (domainInfo != null && domainInfo.isPropagateToEdge()) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - OAuth2DomainUpdateMsg oAuth2DomainUpdateMsg = oAuth2MsgConstructor.constructOAuth2DomainUpdateMsg(msgType, domainInfo); + OAuth2DomainUpdateMsg oAuth2DomainUpdateMsg = EdgeMsgConstructorUtils.constructOAuth2DomainUpdateMsg(msgType, domainInfo); DownlinkMsg.Builder builder = DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addOAuth2DomainUpdateMsg(oAuth2DomainUpdateMsg); domainInfo.getOauth2ClientInfos().forEach(clientInfo -> { OAuth2Client oauth2Client = edgeCtx.getOAuth2ClientService().findOAuth2ClientById(edgeEvent.getTenantId(), clientInfo.getId()); - OAuth2ClientUpdateMsg oAuth2ClientUpdateMsg = oAuth2MsgConstructor.constructOAuth2ClientUpdateMsg(msgType, oauth2Client); + OAuth2ClientUpdateMsg oAuth2ClientUpdateMsg = EdgeMsgConstructorUtils.constructOAuth2ClientUpdateMsg(msgType, oauth2Client); builder.addOAuth2ClientUpdateMsg(oAuth2ClientUpdateMsg); }); return builder.build(); } } case DELETED -> { - OAuth2DomainUpdateMsg oAuth2DomainUpdateMsg = oAuth2MsgConstructor.constructOAuth2DomainDeleteMsg(domainId); + OAuth2DomainUpdateMsg oAuth2DomainUpdateMsg = EdgeMsgConstructorUtils.constructOAuth2DomainDeleteMsg(domainId); return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addOAuth2DomainUpdateMsg(oAuth2DomainUpdateMsg) @@ -80,37 +72,9 @@ public class OAuth2EdgeProcessor extends BaseEdgeProcessor { return null; } - public DownlinkMsg convertOAuth2ClientEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { - if (EdgeVersionUtils.isEdgeVersionOlderThan(edgeVersion, EdgeVersion.V_3_8_0)) { - return null; - } - OAuth2ClientId oAuth2ClientId = new OAuth2ClientId(edgeEvent.getEntityId()); - - switch (edgeEvent.getAction()) { - case ADDED, UPDATED -> { - boolean isPropagateToEdge = edgeCtx.getOAuth2ClientService().isPropagateOAuth2ClientToEdge(edgeEvent.getTenantId(), oAuth2ClientId); - if (!isPropagateToEdge) { - return null; - } - OAuth2Client oAuth2Client = edgeCtx.getOAuth2ClientService().findOAuth2ClientById(edgeEvent.getTenantId(), oAuth2ClientId); - if (oAuth2Client != null) { - UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - OAuth2ClientUpdateMsg oAuth2ClientUpdateMsg = oAuth2MsgConstructor.constructOAuth2ClientUpdateMsg(msgType, oAuth2Client); - return DownlinkMsg.newBuilder() - .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addOAuth2ClientUpdateMsg(oAuth2ClientUpdateMsg) - .build(); - } - } - case DELETED -> { - OAuth2ClientUpdateMsg oAuth2ClientDeleteMsg = oAuth2MsgConstructor.constructOAuth2ClientDeleteMsg(oAuth2ClientId); - return DownlinkMsg.newBuilder() - .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addOAuth2ClientUpdateMsg(oAuth2ClientDeleteMsg) - .build(); - } - } - return null; + @Override + public EdgeEventType getEdgeEventType() { + return EdgeEventType.DOMAIN; } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/OAuth2ClientEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/OAuth2ClientEdgeProcessor.java new file mode 100644 index 0000000000..512f03f666 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/OAuth2ClientEdgeProcessor.java @@ -0,0 +1,73 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc.processor.oauth2; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.EdgeUtils; +import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventType; +import org.thingsboard.server.common.data.id.OAuth2ClientId; +import org.thingsboard.server.common.data.oauth2.OAuth2Client; +import org.thingsboard.server.gen.edge.v1.DownlinkMsg; +import org.thingsboard.server.gen.edge.v1.OAuth2ClientUpdateMsg; +import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; +import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; + +@Slf4j +@Component +@TbCoreComponent +public class OAuth2ClientEdgeProcessor extends BaseEdgeProcessor { + + @Override + public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { + OAuth2ClientId oAuth2ClientId = new OAuth2ClientId(edgeEvent.getEntityId()); + + switch (edgeEvent.getAction()) { + case ADDED, UPDATED -> { + boolean isPropagateToEdge = edgeCtx.getOAuth2ClientService().isPropagateOAuth2ClientToEdge(edgeEvent.getTenantId(), oAuth2ClientId); + if (!isPropagateToEdge) { + return null; + } + OAuth2Client oAuth2Client = edgeCtx.getOAuth2ClientService().findOAuth2ClientById(edgeEvent.getTenantId(), oAuth2ClientId); + if (oAuth2Client != null) { + UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); + OAuth2ClientUpdateMsg oAuth2ClientUpdateMsg = EdgeMsgConstructorUtils.constructOAuth2ClientUpdateMsg(msgType, oAuth2Client); + return DownlinkMsg.newBuilder() + .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) + .addOAuth2ClientUpdateMsg(oAuth2ClientUpdateMsg) + .build(); + } + } + case DELETED -> { + OAuth2ClientUpdateMsg oAuth2ClientDeleteMsg = EdgeMsgConstructorUtils.constructOAuth2ClientDeleteMsg(oAuth2ClientId); + return DownlinkMsg.newBuilder() + .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) + .addOAuth2ClientUpdateMsg(oAuth2ClientDeleteMsg) + .build(); + } + } + return null; + } + + @Override + public EdgeEventType getEdgeEventType() { + return EdgeEventType.OAUTH2_CLIENT; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/ota/OtaPackageEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/ota/OtaPackageEdgeProcessor.java index fc803caf4d..0cc90a3ef2 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/ota/OtaPackageEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/ota/OtaPackageEdgeProcessor.java @@ -16,38 +16,33 @@ package org.thingsboard.server.service.edge.rpc.processor.ota; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.OtaPackage; import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.OtaPackageUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.constructor.ota.OtaPackageMsgConstructor; -import org.thingsboard.server.service.edge.rpc.constructor.ota.OtaPackageMsgConstructorFactory; +import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; -@Component @Slf4j +@Component @TbCoreComponent public class OtaPackageEdgeProcessor extends BaseEdgeProcessor { - @Autowired - private OtaPackageMsgConstructorFactory otaPackageMsgConstructorFactory; - - public DownlinkMsg convertOtaPackageEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { + @Override + public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { OtaPackageId otaPackageId = new OtaPackageId(edgeEvent.getEntityId()); - var msgConstructor = (OtaPackageMsgConstructor) otaPackageMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion); switch (edgeEvent.getAction()) { case ADDED, UPDATED -> { OtaPackage otaPackage = edgeCtx.getOtaPackageService().findOtaPackageById(edgeEvent.getTenantId(), otaPackageId); if (otaPackage != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - OtaPackageUpdateMsg otaPackageUpdateMsg = msgConstructor.constructOtaPackageUpdatedMsg(msgType, otaPackage); + OtaPackageUpdateMsg otaPackageUpdateMsg = EdgeMsgConstructorUtils.constructOtaPackageUpdatedMsg(msgType, otaPackage); return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addOtaPackageUpdateMsg(otaPackageUpdateMsg) @@ -55,7 +50,7 @@ public class OtaPackageEdgeProcessor extends BaseEdgeProcessor { } } case DELETED -> { - OtaPackageUpdateMsg otaPackageUpdateMsg = msgConstructor.constructOtaPackageDeleteMsg(otaPackageId); + OtaPackageUpdateMsg otaPackageUpdateMsg = EdgeMsgConstructorUtils.constructOtaPackageDeleteMsg(otaPackageId); return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addOtaPackageUpdateMsg(otaPackageUpdateMsg) @@ -65,4 +60,9 @@ public class OtaPackageEdgeProcessor extends BaseEdgeProcessor { return null; } + @Override + public EdgeEventType getEdgeEventType() { + return EdgeEventType.OTA_PACKAGE; + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/queue/QueueEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/queue/QueueEdgeProcessor.java index 76f068ac7a..8da1a18f86 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/queue/QueueEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/queue/QueueEdgeProcessor.java @@ -16,19 +16,17 @@ package org.thingsboard.server.service.edge.rpc.processor.queue; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.QueueId; import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.constructor.queue.QueueMsgConstructor; -import org.thingsboard.server.service.edge.rpc.constructor.queue.QueueMsgConstructorFactory; +import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @Slf4j @@ -36,18 +34,15 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @TbCoreComponent public class QueueEdgeProcessor extends BaseEdgeProcessor { - @Autowired - private QueueMsgConstructorFactory queueMsgConstructorFactory; - - public DownlinkMsg convertQueueEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { + @Override + public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { QueueId queueId = new QueueId(edgeEvent.getEntityId()); - var msgConstructor = (QueueMsgConstructor) queueMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion); switch (edgeEvent.getAction()) { case ADDED, UPDATED -> { Queue queue = edgeCtx.getQueueService().findQueueById(edgeEvent.getTenantId(), queueId); if (queue != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - QueueUpdateMsg queueUpdateMsg = msgConstructor.constructQueueUpdatedMsg(msgType, queue); + QueueUpdateMsg queueUpdateMsg = EdgeMsgConstructorUtils.constructQueueUpdatedMsg(msgType, queue); return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addQueueUpdateMsg(queueUpdateMsg) @@ -55,7 +50,7 @@ public class QueueEdgeProcessor extends BaseEdgeProcessor { } } case DELETED -> { - QueueUpdateMsg queueDeleteMsg = msgConstructor.constructQueueDeleteMsg(queueId); + QueueUpdateMsg queueDeleteMsg = EdgeMsgConstructorUtils.constructQueueDeleteMsg(queueId); return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addQueueUpdateMsg(queueDeleteMsg) @@ -65,4 +60,9 @@ public class QueueEdgeProcessor extends BaseEdgeProcessor { return null; } + @Override + public EdgeEventType getEdgeEventType() { + return EdgeEventType.QUEUE; + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/BaseRelationProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/BaseRelationProcessor.java index 0c6da73a25..488e065a26 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/BaseRelationProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/BaseRelationProcessor.java @@ -18,6 +18,7 @@ package org.thingsboard.server.service.edge.rpc.processor.relation; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg; @@ -29,7 +30,7 @@ public abstract class BaseRelationProcessor extends BaseEdgeProcessor { protected ListenableFuture processRelationMsg(TenantId tenantId, RelationUpdateMsg relationUpdateMsg) { log.trace("[{}] processRelationMsg [{}]", tenantId, relationUpdateMsg); try { - EntityRelation entityRelation = constructEntityRelationFromUpdateMsg(relationUpdateMsg); + EntityRelation entityRelation = JacksonUtil.fromString(relationUpdateMsg.getEntity(), EntityRelation.class, true); if (entityRelation == null) { throw new RuntimeException("[{" + tenantId + "}] relationUpdateMsg {" + relationUpdateMsg + "} cannot be converted to entity relation"); } @@ -56,6 +57,4 @@ public abstract class BaseRelationProcessor extends BaseEdgeProcessor { return Futures.immediateFuture(null); } - protected abstract EntityRelation constructEntityRelationFromUpdateMsg(RelationUpdateMsg relationUpdateMsg); - } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java index 4153121b43..4556d4f58f 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java @@ -18,7 +18,7 @@ package org.thingsboard.server.service.edge.rpc.processor.relation; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EntityType; @@ -30,12 +30,11 @@ import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.service.edge.rpc.constructor.relation.RelationMsgConstructor; -import org.thingsboard.server.service.edge.rpc.constructor.relation.RelationMsgConstructorFactory; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; import java.util.ArrayList; import java.util.HashSet; @@ -43,10 +42,9 @@ import java.util.List; import java.util.Set; @Slf4j -public abstract class RelationEdgeProcessor extends BaseRelationProcessor implements RelationProcessor { - - @Autowired - private RelationMsgConstructorFactory relationMsgConstructorFactory; +@Component +@TbCoreComponent +public class RelationEdgeProcessor extends BaseRelationProcessor implements RelationProcessor { @Override public ListenableFuture processRelationMsgFromEdge(TenantId tenantId, Edge edge, RelationUpdateMsg relationUpdateMsg) { @@ -60,18 +58,7 @@ public abstract class RelationEdgeProcessor extends BaseRelationProcessor implem } @Override - public DownlinkMsg convertRelationEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { - EntityRelation entityRelation = JacksonUtil.convertValue(edgeEvent.getBody(), EntityRelation.class); - UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - RelationUpdateMsg relationUpdateMsg = ((RelationMsgConstructor) relationMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)) - .constructRelationUpdatedMsg(msgType, entityRelation); - return DownlinkMsg.newBuilder() - .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addRelationUpdateMsg(relationUpdateMsg) - .build(); - } - - public ListenableFuture processRelationNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { + public ListenableFuture processEntityNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { EntityRelation relation = JacksonUtil.fromString(edgeNotificationMsg.getBody(), EntityRelation.class); if (relation == null) { return Futures.immediateFuture(null); @@ -97,4 +84,20 @@ public abstract class RelationEdgeProcessor extends BaseRelationProcessor implem return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); } + @Override + public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { + EntityRelation entityRelation = JacksonUtil.convertValue(edgeEvent.getBody(), EntityRelation.class); + UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); + RelationUpdateMsg relationUpdateMsg = EdgeMsgConstructorUtils.constructRelationUpdatedMsg(msgType, entityRelation); + return DownlinkMsg.newBuilder() + .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) + .addRelationUpdateMsg(relationUpdateMsg) + .build(); + } + + @Override + public EdgeEventType getEdgeEventType() { + return EdgeEventType.RELATION; + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessorFactory.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessorFactory.java deleted file mode 100644 index 3dea55205e..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessorFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.processor.relation; - -import org.springframework.stereotype.Component; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessorFactory; - -@Component -@TbCoreComponent -public class RelationEdgeProcessorFactory extends BaseEdgeProcessorFactory { - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessorV1.java deleted file mode 100644 index 647cec120a..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessorV1.java +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.processor.relation; - -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.EntityIdFactory; -import org.thingsboard.server.common.data.relation.EntityRelation; -import org.thingsboard.server.common.data.relation.RelationTypeGroup; -import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg; -import org.thingsboard.server.queue.util.TbCoreComponent; - -import java.util.UUID; - -@Component -@TbCoreComponent -public class RelationEdgeProcessorV1 extends RelationEdgeProcessor { - - @Override - protected EntityRelation constructEntityRelationFromUpdateMsg(RelationUpdateMsg relationUpdateMsg) { - EntityRelation entityRelation = new EntityRelation(); - - UUID fromUUID = new UUID(relationUpdateMsg.getFromIdMSB(), relationUpdateMsg.getFromIdLSB()); - EntityId fromId = EntityIdFactory.getByTypeAndUuid(EntityType.valueOf(relationUpdateMsg.getFromEntityType()), fromUUID); - entityRelation.setFrom(fromId); - - UUID toUUID = new UUID(relationUpdateMsg.getToIdMSB(), relationUpdateMsg.getToIdLSB()); - EntityId toId = EntityIdFactory.getByTypeAndUuid(EntityType.valueOf(relationUpdateMsg.getToEntityType()), toUUID); - entityRelation.setTo(toId); - - entityRelation.setType(relationUpdateMsg.getType()); - entityRelation.setTypeGroup(relationUpdateMsg.hasTypeGroup() - ? RelationTypeGroup.valueOf(relationUpdateMsg.getTypeGroup()) : RelationTypeGroup.COMMON); - entityRelation.setAdditionalInfo(JacksonUtil.toJsonNode(relationUpdateMsg.getAdditionalInfo())); - return entityRelation; - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessorV2.java deleted file mode 100644 index 7b31da421d..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessorV2.java +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.processor.relation; - -import org.springframework.context.annotation.Primary; -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.relation.EntityRelation; -import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Primary -@Component -@TbCoreComponent -public class RelationEdgeProcessorV2 extends RelationEdgeProcessor { - - @Override - protected EntityRelation constructEntityRelationFromUpdateMsg(RelationUpdateMsg relationUpdateMsg) { - return JacksonUtil.fromString(relationUpdateMsg.getEntity(), EntityRelation.class, true); - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationProcessor.java index 82a893e9ba..d3d31cd780 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationProcessor.java @@ -17,10 +17,7 @@ package org.thingsboard.server.service.edge.rpc.processor.relation; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg; import org.thingsboard.server.service.edge.rpc.processor.EdgeProcessor; @@ -28,6 +25,4 @@ public interface RelationProcessor extends EdgeProcessor { ListenableFuture processRelationMsgFromEdge(TenantId tenantId, Edge edge, RelationUpdateMsg relationUpdateMsg); - DownlinkMsg convertRelationEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion); - } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/BaseResourceProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/BaseResourceProcessor.java index ac7bd6768c..96c9d717b5 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/BaseResourceProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/BaseResourceProcessor.java @@ -18,6 +18,7 @@ package org.thingsboard.server.service.edge.rpc.processor.resource; import com.datastax.oss.driver.api.core.uuid.Uuids; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TbResource; @@ -38,7 +39,7 @@ public abstract class BaseResourceProcessor extends BaseEdgeProcessor { protected boolean saveOrUpdateTbResource(TenantId tenantId, TbResourceId tbResourceId, ResourceUpdateMsg resourceUpdateMsg) { boolean resourceKeyUpdated = false; try { - TbResource resource = constructResourceFromUpdateMsg(tenantId, tbResourceId, resourceUpdateMsg); + TbResource resource = JacksonUtil.fromString(resourceUpdateMsg.getEntity(), TbResource.class, true); if (resource == null) { throw new RuntimeException("[{" + tenantId + "}] resourceUpdateMsg {" + resourceUpdateMsg + " } cannot be converted to resource"); } @@ -53,6 +54,9 @@ public abstract class BaseResourceProcessor extends BaseEdgeProcessor { } String resourceKey = resource.getResourceKey(); ResourceType resourceType = resource.getResourceType(); + if (!created && !resourceType.isUpdatable()) { + resource.setData(null); + } PageDataIterable resourcesIterable = new PageDataIterable<>( link -> edgeCtx.getResourceService().findTenantResourcesByResourceTypeAndPageLink(tenantId, resourceType, link), 1024); for (TbResource tbResource : resourcesIterable) { @@ -76,6 +80,4 @@ public abstract class BaseResourceProcessor extends BaseEdgeProcessor { return resourceKeyUpdated; } - protected abstract TbResource constructResourceFromUpdateMsg(TenantId tenantId, TbResourceId tbResourceId, ResourceUpdateMsg resourceUpdateMsg); - } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessor.java index 10c58e89d0..1b1829743b 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessor.java @@ -18,7 +18,7 @@ package org.thingsboard.server.service.edge.rpc.processor.resource; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.edge.Edge; @@ -29,19 +29,17 @@ import org.thingsboard.server.common.data.id.TbResourceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.ResourceUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.service.edge.rpc.constructor.resource.ResourceMsgConstructor; -import org.thingsboard.server.service.edge.rpc.constructor.resource.ResourceMsgConstructorFactory; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; import java.util.UUID; @Slf4j -public abstract class ResourceEdgeProcessor extends BaseResourceProcessor implements ResourceProcessor { - - @Autowired - private ResourceMsgConstructorFactory resourceMsgConstructorFactory; +@Component +@TbCoreComponent +public class ResourceEdgeProcessor extends BaseResourceProcessor implements ResourceProcessor { @Override public ListenableFuture processResourceMsgFromEdge(TenantId tenantId, Edge edge, ResourceUpdateMsg resourceUpdateMsg) { @@ -75,23 +73,22 @@ public abstract class ResourceEdgeProcessor extends BaseResourceProcessor implem } @Override - public DownlinkMsg convertResourceEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { + public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { TbResourceId tbResourceId = new TbResourceId(edgeEvent.getEntityId()); - var msgConstructor = (ResourceMsgConstructor) resourceMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion); switch (edgeEvent.getAction()) { case ADDED, UPDATED -> { TbResource tbResource = edgeCtx.getResourceService().findResourceById(edgeEvent.getTenantId(), tbResourceId); if (tbResource != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - ResourceUpdateMsg resourceUpdateMsg = msgConstructor.constructResourceUpdatedMsg(msgType, tbResource); - return resourceUpdateMsg != null ? DownlinkMsg.newBuilder() + ResourceUpdateMsg resourceUpdateMsg = EdgeMsgConstructorUtils.constructResourceUpdatedMsg(msgType, tbResource); + return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addResourceUpdateMsg(resourceUpdateMsg) - .build() : null; + .build(); } } case DELETED -> { - ResourceUpdateMsg resourceUpdateMsg = msgConstructor.constructResourceDeleteMsg(tbResourceId); + ResourceUpdateMsg resourceUpdateMsg = EdgeMsgConstructorUtils.constructResourceDeleteMsg(tbResourceId); return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addResourceUpdateMsg(resourceUpdateMsg) @@ -101,4 +98,9 @@ public abstract class ResourceEdgeProcessor extends BaseResourceProcessor implem return null; } + @Override + public EdgeEventType getEdgeEventType() { + return EdgeEventType.TB_RESOURCE; + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessorFactory.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessorFactory.java deleted file mode 100644 index 9f45aacdeb..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessorFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.processor.resource; - -import org.springframework.stereotype.Component; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessorFactory; - -@Component -@TbCoreComponent -public class ResourceEdgeProcessorFactory extends BaseEdgeProcessorFactory { - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessorV1.java deleted file mode 100644 index f272d447a4..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessorV1.java +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.processor.resource; - -import com.datastax.oss.driver.api.core.uuid.Uuids; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.ResourceType; -import org.thingsboard.server.common.data.TbResource; -import org.thingsboard.server.common.data.id.TbResourceId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.gen.edge.v1.ResourceUpdateMsg; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Component -@TbCoreComponent -public class ResourceEdgeProcessorV1 extends ResourceEdgeProcessor { - - @Override - protected TbResource constructResourceFromUpdateMsg(TenantId tenantId, TbResourceId tbResourceId, ResourceUpdateMsg resourceUpdateMsg) { - TbResource resource = new TbResource(); - if (resourceUpdateMsg.getIsSystem()) { - resource.setTenantId(TenantId.SYS_TENANT_ID); - } else { - resource.setTenantId(tenantId); - } - resource.setCreatedTime(Uuids.unixTimestamp(tbResourceId.getId())); - resource.setTitle(resourceUpdateMsg.getTitle()); - resource.setResourceKey(resourceUpdateMsg.getResourceKey()); - resource.setResourceType(ResourceType.valueOf(resourceUpdateMsg.getResourceType())); - resource.setFileName(resourceUpdateMsg.getFileName()); - resource.setEncodedData(resourceUpdateMsg.hasData() ? resourceUpdateMsg.getData() : null); - resource.setEtag(resourceUpdateMsg.hasEtag() ? resourceUpdateMsg.getEtag() : null); - return resource; - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessorV2.java deleted file mode 100644 index 108a6c8ecb..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessorV2.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.processor.resource; - -import org.springframework.context.annotation.Primary; -import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.TbResource; -import org.thingsboard.server.common.data.id.TbResourceId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.gen.edge.v1.ResourceUpdateMsg; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Primary -@Component -@TbCoreComponent -public class ResourceEdgeProcessorV2 extends ResourceEdgeProcessor { - - @Override - protected TbResource constructResourceFromUpdateMsg(TenantId tenantId, TbResourceId tbResourceId, ResourceUpdateMsg resourceUpdateMsg) { - return JacksonUtil.fromString(resourceUpdateMsg.getEntity(), TbResource.class, true); - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceProcessor.java index a52d31a98b..5ed1f7c363 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceProcessor.java @@ -17,10 +17,7 @@ package org.thingsboard.server.service.edge.rpc.processor.resource; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.ResourceUpdateMsg; import org.thingsboard.server.service.edge.rpc.processor.EdgeProcessor; @@ -28,6 +25,4 @@ public interface ResourceProcessor extends EdgeProcessor { ListenableFuture processResourceMsgFromEdge(TenantId tenantId, Edge edge, ResourceUpdateMsg resourceUpdateMsg); - DownlinkMsg convertResourceEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion); - } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java index 6df069e400..80b0963d0a 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java @@ -16,22 +16,20 @@ package org.thingsboard.server.service.edge.rpc.processor.rule; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.constructor.rule.RuleChainMsgConstructor; -import org.thingsboard.server.service.edge.rpc.constructor.rule.RuleChainMsgConstructorFactory; +import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; import static org.thingsboard.server.dao.edge.EdgeServiceImpl.EDGE_IS_ROOT_BODY_KEY; @@ -41,13 +39,10 @@ import static org.thingsboard.server.dao.edge.EdgeServiceImpl.EDGE_IS_ROOT_BODY_ @TbCoreComponent public class RuleChainEdgeProcessor extends BaseEdgeProcessor { - @Autowired - private RuleChainMsgConstructorFactory ruleChainMsgConstructorFactory; - - public DownlinkMsg convertRuleChainEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { + @Override + public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { RuleChainId ruleChainId = new RuleChainId(edgeEvent.getEntityId()); DownlinkMsg downlinkMsg = null; - var msgConstructor = (RuleChainMsgConstructor) ruleChainMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion); switch (edgeEvent.getAction()) { case ADDED, UPDATED, ASSIGNED_TO_EDGE -> { RuleChain ruleChain = edgeCtx.getRuleChainService().findRuleChainById(edgeEvent.getTenantId(), ruleChainId); @@ -64,46 +59,30 @@ public class RuleChainEdgeProcessor extends BaseEdgeProcessor { isRoot = edge.getRootRuleChainId().equals(ruleChainId); } UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - RuleChainUpdateMsg ruleChainUpdateMsg = msgConstructor.constructRuleChainUpdatedMsg(msgType, ruleChain, isRoot); + RuleChainUpdateMsg ruleChainUpdateMsg = EdgeMsgConstructorUtils.constructRuleChainUpdatedMsg(msgType, ruleChain, isRoot); DownlinkMsg.Builder builder = DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addRuleChainUpdateMsg(ruleChainUpdateMsg); RuleChainMetaData ruleChainMetaData = edgeCtx.getRuleChainService().loadRuleChainMetaData(edgeEvent.getTenantId(), ruleChainId); - RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = msgConstructor - .constructRuleChainMetadataUpdatedMsg(edgeEvent.getTenantId(), msgType, ruleChainMetaData, edgeVersion); - if (ruleChainMetadataUpdateMsg != null) { - builder.addRuleChainMetadataUpdateMsg(ruleChainMetadataUpdateMsg); - } + RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = EdgeMsgConstructorUtils + .constructRuleChainMetadataUpdatedMsg(msgType, ruleChainMetaData); + builder.addRuleChainMetadataUpdateMsg(ruleChainMetadataUpdateMsg); downlinkMsg = builder.build(); } } case DELETED, UNASSIGNED_FROM_EDGE -> downlinkMsg = DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addRuleChainUpdateMsg(msgConstructor.constructRuleChainDeleteMsg(ruleChainId)) + .addRuleChainUpdateMsg(EdgeMsgConstructorUtils.constructRuleChainDeleteMsg(ruleChainId)) .build(); } return downlinkMsg; } - public DownlinkMsg convertRuleChainMetadataEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { - RuleChainId ruleChainId = new RuleChainId(edgeEvent.getEntityId()); - RuleChain ruleChain = edgeCtx.getRuleChainService().findRuleChainById(edgeEvent.getTenantId(), ruleChainId); - var msgConstructor = (RuleChainMsgConstructor) ruleChainMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion); - if (ruleChain != null) { - RuleChainMetaData ruleChainMetaData = edgeCtx.getRuleChainService().loadRuleChainMetaData(edgeEvent.getTenantId(), ruleChainId); - UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = msgConstructor - .constructRuleChainMetadataUpdatedMsg(edgeEvent.getTenantId(), msgType, ruleChainMetaData, edgeVersion); - if (ruleChainMetadataUpdateMsg != null) { - return DownlinkMsg.newBuilder() - .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addRuleChainMetadataUpdateMsg(ruleChainMetadataUpdateMsg) - .build(); - } - } - return null; + @Override + public EdgeEventType getEdgeEventType() { + return EdgeEventType.RULE_CHAIN; } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainMetadataEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainMetadataEdgeProcessor.java new file mode 100644 index 0000000000..f85045093c --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainMetadataEdgeProcessor.java @@ -0,0 +1,60 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc.processor.rule; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.EdgeUtils; +import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventType; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.gen.edge.v1.DownlinkMsg; +import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; +import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; +import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; + +@Slf4j +@Component +@TbCoreComponent +public class RuleChainMetadataEdgeProcessor extends BaseEdgeProcessor { + + @Override + public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { + RuleChainId ruleChainId = new RuleChainId(edgeEvent.getEntityId()); + RuleChain ruleChain = edgeCtx.getRuleChainService().findRuleChainById(edgeEvent.getTenantId(), ruleChainId); + if (ruleChain != null) { + RuleChainMetaData ruleChainMetaData = edgeCtx.getRuleChainService().loadRuleChainMetaData(edgeEvent.getTenantId(), ruleChainId); + UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); + RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = EdgeMsgConstructorUtils + .constructRuleChainMetadataUpdatedMsg(msgType, ruleChainMetaData); + return DownlinkMsg.newBuilder() + .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) + .addRuleChainMetadataUpdateMsg(ruleChainMetadataUpdateMsg) + .build(); + } + return null; + } + + @Override + public EdgeEventType getEdgeEventType() { + return EdgeEventType.RULE_CHAIN_METADATA; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/settings/AdminSettingsEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/settings/AdminSettingsEdgeProcessor.java index 3c0d4fb99c..2dc6f0e3f8 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/settings/AdminSettingsEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/settings/AdminSettingsEdgeProcessor.java @@ -16,18 +16,15 @@ package org.thingsboard.server.service.edge.rpc.processor.settings; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.gen.edge.v1.AdminSettingsUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.constructor.settings.AdminSettingsMsgConstructor; -import org.thingsboard.server.service.edge.rpc.constructor.settings.AdminSettingsMsgConstructorFactory; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @Slf4j @@ -35,20 +32,22 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @TbCoreComponent public class AdminSettingsEdgeProcessor extends BaseEdgeProcessor { - @Autowired - private AdminSettingsMsgConstructorFactory adminSettingsMsgConstructorFactory; - - public DownlinkMsg convertAdminSettingsEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { + @Override + public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { AdminSettings adminSettings = JacksonUtil.convertValue(edgeEvent.getBody(), AdminSettings.class); if (adminSettings == null) { return null; } - AdminSettingsUpdateMsg adminSettingsUpdateMsg = ((AdminSettingsMsgConstructor) - adminSettingsMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructAdminSettingsUpdateMsg(adminSettings); + AdminSettingsUpdateMsg msg = AdminSettingsUpdateMsg.newBuilder().setEntity(JacksonUtil.toString(adminSettings)).build(); return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addAdminSettingsUpdateMsg(adminSettingsUpdateMsg) + .addAdminSettingsUpdateMsg(msg) .build(); } + @Override + public EdgeEventType getEdgeEventType() { + return EdgeEventType.ADMIN_SETTINGS; + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java index 5e7731db1d..733755bfbd 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java @@ -73,7 +73,7 @@ import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; -import org.thingsboard.server.service.edge.rpc.constructor.telemetry.EntityDataMsgConstructor; +import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; import org.thingsboard.server.service.profile.TbAssetProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache; @@ -89,9 +89,6 @@ import java.util.stream.Collectors; @Slf4j public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { - @Autowired - private EntityDataMsgConstructor entityDataMsgConstructor; - @Autowired private PartitionService partitionService; @@ -428,8 +425,7 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { } } String bodyJackson = JacksonUtil.toString(body); - return bodyJackson == null ? null : - entityDataMsgConstructor.constructEntityDataMsg(tenantId, entityId, actionType, JsonParser.parseString(bodyJackson)); + return bodyJackson == null ? null : EdgeMsgConstructorUtils.constructEntityDataMsg(tenantId, entityId, actionType, JsonParser.parseString(bodyJackson)); } private ListenableFuture> filterAttributesByTs(TenantId tenantId, EntityId entityId, AttributeScope scope, diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantEdgeProcessor.java index aa6795b123..4234e0ca32 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantEdgeProcessor.java @@ -16,22 +16,20 @@ package org.thingsboard.server.service.edge.rpc.processor.tenant; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.TenantProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.TenantUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.constructor.tenant.TenantMsgConstructor; -import org.thingsboard.server.service.edge.rpc.constructor.tenant.TenantMsgConstructorFactory; +import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @Slf4j @@ -39,19 +37,16 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @TbCoreComponent public class TenantEdgeProcessor extends BaseEdgeProcessor { - @Autowired - private TenantMsgConstructorFactory tenantMsgConstructorFactory; - - public DownlinkMsg convertTenantEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { + @Override + public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { TenantId tenantId = TenantId.fromUUID(edgeEvent.getEntityId()); - var msgConstructor = ((TenantMsgConstructor) tenantMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)); if (EdgeEventActionType.UPDATED.equals(edgeEvent.getAction())) { Tenant tenant = edgeCtx.getTenantService().findTenantById(tenantId); if (tenant != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - TenantUpdateMsg tenantUpdateMsg = msgConstructor.constructTenantUpdateMsg(msgType, tenant); + TenantUpdateMsg tenantUpdateMsg = EdgeMsgConstructorUtils.constructTenantUpdateMsg(msgType, tenant); TenantProfile tenantProfile = edgeCtx.getTenantProfileService().findTenantProfileById(tenantId, tenant.getTenantProfileId()); - TenantProfileUpdateMsg tenantProfileUpdateMsg = msgConstructor.constructTenantProfileUpdateMsg(msgType, tenantProfile, edgeVersion); + TenantProfileUpdateMsg tenantProfileUpdateMsg = EdgeMsgConstructorUtils.constructTenantProfileUpdateMsg(msgType, tenantProfile); return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addTenantUpdateMsg(tenantUpdateMsg) @@ -62,4 +57,9 @@ public class TenantEdgeProcessor extends BaseEdgeProcessor { return null; } + @Override + public EdgeEventType getEdgeEventType() { + return EdgeEventType.TENANT; + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantProfileEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantProfileEdgeProcessor.java index 07ba6039af..0312389e0d 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantProfileEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantProfileEdgeProcessor.java @@ -16,20 +16,18 @@ package org.thingsboard.server.service.edge.rpc.processor.tenant; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.TenantProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.constructor.tenant.TenantMsgConstructor; -import org.thingsboard.server.service.edge.rpc.constructor.tenant.TenantMsgConstructorFactory; +import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @Slf4j @@ -37,17 +35,14 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @TbCoreComponent public class TenantProfileEdgeProcessor extends BaseEdgeProcessor { - @Autowired - private TenantMsgConstructorFactory tenantMsgConstructorFactory; - - public DownlinkMsg convertTenantProfileEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { + @Override + public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { TenantProfileId tenantProfileId = new TenantProfileId(edgeEvent.getEntityId()); - var msgConstructor = ((TenantMsgConstructor) tenantMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)); if (EdgeEventActionType.UPDATED.equals(edgeEvent.getAction())) { TenantProfile tenantProfile = edgeCtx.getTenantProfileService().findTenantProfileById(edgeEvent.getTenantId(), tenantProfileId); if (tenantProfile != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - TenantProfileUpdateMsg tenantProfileUpdateMsg = msgConstructor.constructTenantProfileUpdateMsg(msgType, tenantProfile, edgeVersion); + TenantProfileUpdateMsg tenantProfileUpdateMsg = EdgeMsgConstructorUtils.constructTenantProfileUpdateMsg(msgType, tenantProfile); return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addTenantProfileUpdateMsg(tenantProfileUpdateMsg) @@ -57,4 +52,9 @@ public class TenantProfileEdgeProcessor extends BaseEdgeProcessor { return null; } + @Override + public EdgeEventType getEdgeEventType() { + return EdgeEventType.TENANT_PROFILE; + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/user/UserEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/user/UserEdgeProcessor.java index 319812a33b..f7ab19af60 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/user/UserEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/user/UserEdgeProcessor.java @@ -16,20 +16,18 @@ package org.thingsboard.server.service.edge.rpc.processor.user; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UserCredentialsUpdateMsg; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.constructor.user.UserMsgConstructor; -import org.thingsboard.server.service.edge.rpc.constructor.user.UserMsgConstructorFactory; +import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @Slf4j @@ -37,12 +35,9 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @TbCoreComponent public class UserEdgeProcessor extends BaseEdgeProcessor { - @Autowired - private UserMsgConstructorFactory userMsgConstructorFactory; - - public DownlinkMsg convertUserEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { + @Override + public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { UserId userId = new UserId(edgeEvent.getEntityId()); - var msgConstructor = (UserMsgConstructor) userMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion); switch (edgeEvent.getAction()) { case ADDED, UPDATED -> { User user = edgeCtx.getUserService().findUserById(edgeEvent.getTenantId(), userId); @@ -50,10 +45,10 @@ public class UserEdgeProcessor extends BaseEdgeProcessor { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); DownlinkMsg.Builder builder = DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addUserUpdateMsg(msgConstructor.constructUserUpdatedMsg(msgType, user)); + .addUserUpdateMsg(EdgeMsgConstructorUtils.constructUserUpdatedMsg(msgType, user)); UserCredentials userCredentialsByUserId = edgeCtx.getUserService().findUserCredentialsByUserId(edgeEvent.getTenantId(), userId); if (userCredentialsByUserId != null && userCredentialsByUserId.isEnabled()) { - builder.addUserCredentialsUpdateMsg(msgConstructor.constructUserCredentialsUpdatedMsg(userCredentialsByUserId)); + builder.addUserCredentialsUpdateMsg(EdgeMsgConstructorUtils.constructUserCredentialsUpdatedMsg(userCredentialsByUserId)); } return builder.build(); } @@ -61,13 +56,13 @@ public class UserEdgeProcessor extends BaseEdgeProcessor { case DELETED -> { return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addUserUpdateMsg(msgConstructor.constructUserDeleteMsg(userId)) + .addUserUpdateMsg(EdgeMsgConstructorUtils.constructUserDeleteMsg(userId)) .build(); } case CREDENTIALS_UPDATED -> { UserCredentials userCredentialsByUserId = edgeCtx.getUserService().findUserCredentialsByUserId(edgeEvent.getTenantId(), userId); if (userCredentialsByUserId != null && userCredentialsByUserId.isEnabled()) { - UserCredentialsUpdateMsg userCredentialsUpdateMsg = msgConstructor.constructUserCredentialsUpdatedMsg(userCredentialsByUserId); + UserCredentialsUpdateMsg userCredentialsUpdateMsg = EdgeMsgConstructorUtils.constructUserCredentialsUpdatedMsg(userCredentialsByUserId); return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addUserCredentialsUpdateMsg(userCredentialsUpdateMsg) @@ -78,4 +73,9 @@ public class UserEdgeProcessor extends BaseEdgeProcessor { return null; } + @Override + public EdgeEventType getEdgeEventType() { + return EdgeEventType.USER; + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetBundleEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetBundleEdgeProcessor.java index 87eece2841..24217d80e2 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetBundleEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetBundleEdgeProcessor.java @@ -16,19 +16,17 @@ package org.thingsboard.server.service.edge.rpc.processor.widget; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.WidgetsBundleId; import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.constructor.widget.WidgetMsgConstructor; -import org.thingsboard.server.service.edge.rpc.constructor.widget.WidgetMsgConstructorFactory; +import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; import java.util.List; @@ -38,19 +36,16 @@ import java.util.List; @TbCoreComponent public class WidgetBundleEdgeProcessor extends BaseEdgeProcessor { - @Autowired - private WidgetMsgConstructorFactory widgetMsgConstructorFactory; - - public DownlinkMsg convertWidgetsBundleEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { + @Override + public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { WidgetsBundleId widgetsBundleId = new WidgetsBundleId(edgeEvent.getEntityId()); - var msgConstructor = ((WidgetMsgConstructor) widgetMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)); switch (edgeEvent.getAction()) { case ADDED, UPDATED -> { WidgetsBundle widgetsBundle = edgeCtx.getWidgetsBundleService().findWidgetsBundleById(edgeEvent.getTenantId(), widgetsBundleId); if (widgetsBundle != null) { List widgets = edgeCtx.getWidgetTypeService().findWidgetFqnsByWidgetsBundleId(edgeEvent.getTenantId(), widgetsBundleId); UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - WidgetsBundleUpdateMsg widgetsBundleUpdateMsg = msgConstructor.constructWidgetsBundleUpdateMsg(msgType, widgetsBundle, widgets); + WidgetsBundleUpdateMsg widgetsBundleUpdateMsg = EdgeMsgConstructorUtils.constructWidgetsBundleUpdateMsg(msgType, widgetsBundle, widgets); return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addWidgetsBundleUpdateMsg(widgetsBundleUpdateMsg) @@ -58,7 +53,7 @@ public class WidgetBundleEdgeProcessor extends BaseEdgeProcessor { } } case DELETED -> { - WidgetsBundleUpdateMsg widgetsBundleUpdateMsg = msgConstructor.constructWidgetsBundleDeleteMsg(widgetsBundleId); + WidgetsBundleUpdateMsg widgetsBundleUpdateMsg = EdgeMsgConstructorUtils.constructWidgetsBundleDeleteMsg(widgetsBundleId); return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addWidgetsBundleUpdateMsg(widgetsBundleUpdateMsg) @@ -68,4 +63,9 @@ public class WidgetBundleEdgeProcessor extends BaseEdgeProcessor { return null; } + @Override + public EdgeEventType getEdgeEventType() { + return EdgeEventType.WIDGETS_BUNDLE; + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetTypeEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetTypeEdgeProcessor.java index 10d2ce4e82..c626a9ad0a 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetTypeEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetTypeEdgeProcessor.java @@ -16,19 +16,17 @@ package org.thingsboard.server.service.edge.rpc.processor.widget; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.constructor.widget.WidgetMsgConstructor; -import org.thingsboard.server.service.edge.rpc.constructor.widget.WidgetMsgConstructorFactory; +import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @Slf4j @@ -36,18 +34,15 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @TbCoreComponent public class WidgetTypeEdgeProcessor extends BaseEdgeProcessor { - @Autowired - private WidgetMsgConstructorFactory widgetMsgConstructorFactory; - - public DownlinkMsg convertWidgetTypeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { + @Override + public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { WidgetTypeId widgetTypeId = new WidgetTypeId(edgeEvent.getEntityId()); - var msgConstructor = (WidgetMsgConstructor) widgetMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion); switch (edgeEvent.getAction()) { case ADDED, UPDATED -> { WidgetTypeDetails widgetTypeDetails = edgeCtx.getWidgetTypeService().findWidgetTypeDetailsById(edgeEvent.getTenantId(), widgetTypeId); if (widgetTypeDetails != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - WidgetTypeUpdateMsg widgetTypeUpdateMsg = msgConstructor.constructWidgetTypeUpdateMsg(msgType, widgetTypeDetails, edgeVersion); + WidgetTypeUpdateMsg widgetTypeUpdateMsg = EdgeMsgConstructorUtils.constructWidgetTypeUpdateMsg(msgType, widgetTypeDetails); return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addWidgetTypeUpdateMsg(widgetTypeUpdateMsg) @@ -55,7 +50,7 @@ public class WidgetTypeEdgeProcessor extends BaseEdgeProcessor { } } case DELETED -> { - WidgetTypeUpdateMsg widgetTypeUpdateMsg = msgConstructor.constructWidgetTypeDeleteMsg(widgetTypeId); + WidgetTypeUpdateMsg widgetTypeUpdateMsg = EdgeMsgConstructorUtils.constructWidgetTypeDeleteMsg(widgetTypeId); return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addWidgetTypeUpdateMsg(widgetTypeUpdateMsg) @@ -65,4 +60,9 @@ public class WidgetTypeEdgeProcessor extends BaseEdgeProcessor { return null; } + @Override + public EdgeEventType getEdgeEventType() { + return EdgeEventType.WIDGET_TYPE; + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/EdgeRequestsService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/EdgeRequestsService.java index 6d47c16680..e04d4d01af 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/EdgeRequestsService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/EdgeRequestsService.java @@ -28,17 +28,22 @@ import org.thingsboard.server.gen.edge.v1.WidgetBundleTypesRequestMsg; public interface EdgeRequestsService { + @Deprecated(since = "3.9.1", forRemoval = true) ListenableFuture processRuleChainMetadataRequestMsg(TenantId tenantId, Edge edge, RuleChainMetadataRequestMsg ruleChainMetadataRequestMsg); ListenableFuture processAttributesRequestMsg(TenantId tenantId, Edge edge, AttributesRequestMsg attributesRequestMsg); ListenableFuture processRelationRequestMsg(TenantId tenantId, Edge edge, RelationRequestMsg relationRequestMsg); + @Deprecated(since = "3.9.1", forRemoval = true) ListenableFuture processDeviceCredentialsRequestMsg(TenantId tenantId, Edge edge, DeviceCredentialsRequestMsg deviceCredentialsRequestMsg); + @Deprecated(since = "3.9.1", forRemoval = true) ListenableFuture processUserCredentialsRequestMsg(TenantId tenantId, Edge edge, UserCredentialsRequestMsg userCredentialsRequestMsg); + @Deprecated(since = "3.9.1", forRemoval = true) ListenableFuture processWidgetBundleTypesRequestMsg(TenantId tenantId, Edge edge, WidgetBundleTypesRequestMsg widgetBundleTypesRequestMsg); + @Deprecated(since = "3.9.1", forRemoval = true) ListenableFuture processEntityViewsRequestMsg(TenantId tenantId, Edge edge, EntityViewsRequestMsg entityViewsRequestMsg); } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DashboardSyncService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DashboardSyncService.java index 21eec71388..2adb6822dc 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DashboardSyncService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DashboardSyncService.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.entitiy.dashboard; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; @@ -30,6 +31,7 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.util.AfterStartUp; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.install.ProjectInfo; import org.thingsboard.server.service.sync.GitSyncService; import org.thingsboard.server.service.sync.vc.GitRepository.FileType; import org.thingsboard.server.service.sync.vc.GitRepository.RepoFile; @@ -51,10 +53,11 @@ public class DashboardSyncService { private final ImageService imageService; private final WidgetsBundleService widgetsBundleService; private final PartitionService partitionService; + private final ProjectInfo projectInfo; @Value("${transport.gateway.dashboard.sync.repository_url:}") private String repoUrl; - @Value("${transport.gateway.dashboard.sync.branch:main}") + @Value("${transport.gateway.dashboard.sync.branch:}") private String branch; @Value("${transport.gateway.dashboard.sync.fetch_frequency:24}") private int fetchFrequencyHours; @@ -64,6 +67,9 @@ public class DashboardSyncService { @AfterStartUp(order = AfterStartUp.REGULAR_SERVICE) public void init() throws Exception { + if (StringUtils.isBlank(branch)) { + branch = "release/" + projectInfo.getProjectVersion(); + } gitSyncService.registerSync(REPO_KEY, repoUrl, branch, TimeUnit.HOURS.toMillis(fetchFrequencyHours), this::update); } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java index 686e85cb1e..2c384aa493 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java @@ -348,7 +348,7 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen .tenantId(entityView.getTenantId()) .entityId(entityId) .entries(latestValues) - .onlyLatest(true) + .strategy(TimeseriesSaveRequest.Strategy.LATEST_AND_WS) .callback(new FutureCallback() { @Override public void onSuccess(@Nullable Void tmp) { diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java index f1d9259e91..3b21685d22 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java @@ -17,9 +17,6 @@ package org.thingsboard.server.service.install; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.info.BuildProperties; import org.springframework.context.annotation.Profile; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; @@ -33,37 +30,29 @@ import java.util.List; @RequiredArgsConstructor public class DefaultDatabaseSchemaSettingsService implements DatabaseSchemaSettingsService { - private static final String CURRENT_PRODUCT = "CE"; // This list should include all versions which are compatible for the upgrade. // The compatibility cycle usually breaks when we have some scripts written in Java that may not work after new release. - private static final List SUPPORTED_VERSIONS_FOR_UPGRADE = List.of("3.8.0", "3.8.1"); + private static final List SUPPORTED_VERSIONS_FOR_UPGRADE = List.of("3.9.0"); - private final BuildProperties buildProperties; + private final ProjectInfo projectInfo; private final JdbcTemplate jdbcTemplate; - @Value("${install.upgrade.from_version:}") - private String upgradeFromVersion; - private String packageSchemaVersion; private String schemaVersionFromDb; @Override public void validateSchemaSettings() { - //TODO: remove after release (3.9.0) - createProductIfNotExists(); - - String dbSchemaVersion = getDbSchemaVersion(); - if (DefaultDataUpdateService.getEnv("SKIP_SCHEMA_VERSION_CHECK", false)) { log.info("Skipped DB schema version check due to SKIP_SCHEMA_VERSION_CHECK set to 'true'."); return; } String product = getProductFromDb(); - if (!CURRENT_PRODUCT.equals(product)) { - onSchemaSettingsError(String.format("Upgrade failed: can't upgrade ThingsBoard %s database using ThingsBoard %s.", product, CURRENT_PRODUCT)); + if (!projectInfo.getProductType().equals(product)) { + onSchemaSettingsError(String.format("Upgrade failed: can't upgrade ThingsBoard %s database using ThingsBoard %s.", product, projectInfo.getProductType())); } + String dbSchemaVersion = getDbSchemaVersion(); if (dbSchemaVersion.equals(getPackageSchemaVersion())) { onSchemaSettingsError("Upgrade failed: database already upgraded to current version. You can set SKIP_SCHEMA_VERSION_CHECK to 'true' if force re-upgrade needed."); } @@ -75,19 +64,11 @@ public class DefaultDatabaseSchemaSettingsService implements DatabaseSchemaSetti } } - @Deprecated(forRemoval = true, since = "3.9.0") - private void createProductIfNotExists() { - boolean isCommunityEdition = jdbcTemplate.queryForList( - "SELECT 1 FROM information_schema.tables WHERE table_name = 'integration'", Integer.class).isEmpty(); - String product = isCommunityEdition ? "CE" : "PE"; - jdbcTemplate.execute("ALTER TABLE tb_schema_settings ADD COLUMN IF NOT EXISTS product varchar(2) DEFAULT '" + product + "'"); - } - @Override public void createSchemaSettings() { Long schemaVersion = getSchemaVersionFromDb(); if (schemaVersion == null) { - jdbcTemplate.execute("INSERT INTO tb_schema_settings (schema_version, product) VALUES (" + getPackageSchemaVersionForDb() + ", '" + CURRENT_PRODUCT + "')"); + jdbcTemplate.execute("INSERT INTO tb_schema_settings (schema_version, product) VALUES (" + getPackageSchemaVersionForDb() + ", '" + projectInfo.getProductType() + "')"); } } @@ -99,7 +80,7 @@ public class DefaultDatabaseSchemaSettingsService implements DatabaseSchemaSetti @Override public String getPackageSchemaVersion() { if (packageSchemaVersion == null) { - packageSchemaVersion = buildProperties.getVersion().replaceAll("[^\\d.]", ""); + packageSchemaVersion = projectInfo.getProjectVersion(); } return packageSchemaVersion; } @@ -107,15 +88,6 @@ public class DefaultDatabaseSchemaSettingsService implements DatabaseSchemaSetti @Override public String getDbSchemaVersion() { if (schemaVersionFromDb == null) { - if (StringUtils.isNotBlank(upgradeFromVersion)) { - /* - * TODO - Remove after the release of 3.9.0: - * This a temporary workaround due to the issue that schema version in the - * tb_schema_settings was set as 3.6.4 during the install of 3.8.1. - * */ - schemaVersionFromDb = upgradeFromVersion; - return schemaVersionFromDb; - } Long version = getSchemaVersionFromDb(); if (version == null) { onSchemaSettingsError("Upgrade failed: the database schema version is missing."); diff --git a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java index a6ccb85e11..b230be0d57 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java +++ b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java @@ -17,8 +17,6 @@ package org.thingsboard.server.service.install; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; -import lombok.Getter; -import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -120,11 +118,6 @@ public class InstallScripts { @Autowired private ResourcesUpdater resourcesUpdater; - @Getter @Setter - private boolean updateImages = false; - - @Getter @Setter - private boolean updateResourcesUsage = false; @Autowired private ImageService imageService; @@ -395,14 +388,6 @@ public class InstallScripts { } } - public void updateImages() { - resourcesUpdater.updateWidgetsBundlesImages(); - resourcesUpdater.updateWidgetTypesImages(); - resourcesUpdater.updateDashboardsImages(); - resourcesUpdater.updateDeviceProfilesImages(); - resourcesUpdater.updateAssetProfilesImages(); - } - public void loadSystemImagesAndResources() { log.info("Loading system images and resources..."); Stream dashboardsFiles = Stream.concat(listDir(Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, DASHBOARDS_DIR)), @@ -512,11 +497,6 @@ public class InstallScripts { } } - public void updateResourcesUsage() { - resourcesUpdater.updateDashboardsResources(); - resourcesUpdater.updateWidgetsResources(); - } - private void loadSystemResources(Path dir, ResourceType resourceType, ResourceSubType resourceSubType) { listDir(dir).forEach(resourceFile -> { String resourceKey = resourceFile.getFileName().toString(); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/ota/OtaPackageMsgConstructorFactory.java b/application/src/main/java/org/thingsboard/server/service/install/ProjectInfo.java similarity index 61% rename from application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/ota/OtaPackageMsgConstructorFactory.java rename to application/src/main/java/org/thingsboard/server/service/install/ProjectInfo.java index 664ec1d60b..eddd4c8ccf 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/ota/OtaPackageMsgConstructorFactory.java +++ b/application/src/main/java/org/thingsboard/server/service/install/ProjectInfo.java @@ -13,14 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.edge.rpc.constructor.ota; +package org.thingsboard.server.service.install; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.info.BuildProperties; import org.springframework.stereotype.Component; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.constructor.BaseMsgConstructorFactory; @Component -@TbCoreComponent -public class OtaPackageMsgConstructorFactory extends BaseMsgConstructorFactory { +@RequiredArgsConstructor +public class ProjectInfo { + + private final BuildProperties buildProperties; + + public String getProjectVersion() { + return buildProperties.getVersion().replaceAll("[^\\d.]", ""); + } + + public String getProductType() { + return "CE"; + } } diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java index e8622fae37..cea938bce9 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java @@ -34,7 +34,6 @@ import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.sql.JpaExecutorService; import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.component.RuleNodeClassInfo; -import org.thingsboard.server.service.install.InstallScripts; import org.thingsboard.server.utils.TbNodeUpgradeUtils; import java.util.ArrayList; @@ -58,9 +57,6 @@ public class DefaultDataUpdateService implements DataUpdateService { @Autowired JpaExecutorService jpaExecutorService; - @Autowired - private InstallScripts installScripts; - @Override public void updateData() throws Exception { log.info("Updating data ..."); diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbEdgeConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbEdgeConsumerService.java index f219d7ae69..68df0c5424 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbEdgeConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbEdgeConsumerService.java @@ -247,36 +247,7 @@ public class DefaultTbEdgeConsumerService extends AbstractConsumerService future; - switch (type) { - case EDGE -> future = edgeCtx.getEdgeProcessor().processEdgeNotification(tenantId, edgeNotificationMsg); - case ASSET -> future = edgeCtx.getAssetProcessor().processEntityNotification(tenantId, edgeNotificationMsg); - case ASSET_PROFILE -> future = edgeCtx.getAssetProfileProcessor().processEntityNotification(tenantId, edgeNotificationMsg); - case DEVICE -> future = edgeCtx.getDeviceProcessor().processEntityNotification(tenantId, edgeNotificationMsg); - case DEVICE_PROFILE -> future = edgeCtx.getDeviceProfileProcessor().processEntityNotification(tenantId, edgeNotificationMsg); - case ENTITY_VIEW -> future = edgeCtx.getEntityViewProcessor().processEntityNotification(tenantId, edgeNotificationMsg); - case DASHBOARD -> future = edgeCtx.getDashboardProcessor().processEntityNotification(tenantId, edgeNotificationMsg); - case RULE_CHAIN -> future = edgeCtx.getRuleChainProcessor().processEntityNotification(tenantId, edgeNotificationMsg); - case USER -> future = edgeCtx.getUserProcessor().processEntityNotification(tenantId, edgeNotificationMsg); - case CUSTOMER -> future = edgeCtx.getCustomerProcessor().processCustomerNotification(tenantId, edgeNotificationMsg); - case OTA_PACKAGE -> future = edgeCtx.getOtaPackageProcessor().processEntityNotification(tenantId, edgeNotificationMsg); - case WIDGETS_BUNDLE -> future = edgeCtx.getWidgetBundleProcessor().processEntityNotification(tenantId, edgeNotificationMsg); - case WIDGET_TYPE -> future = edgeCtx.getWidgetTypeProcessor().processEntityNotification(tenantId, edgeNotificationMsg); - case QUEUE -> future = edgeCtx.getQueueProcessor().processEntityNotification(tenantId, edgeNotificationMsg); - case ALARM -> future = edgeCtx.getAlarmProcessor().processAlarmNotification(tenantId, edgeNotificationMsg); - case ALARM_COMMENT -> future = edgeCtx.getAlarmProcessor().processAlarmCommentNotification(tenantId, edgeNotificationMsg); - case RELATION -> future = edgeCtx.getRelationProcessor().processRelationNotification(tenantId, edgeNotificationMsg); - case TENANT -> future = edgeCtx.getTenantProcessor().processEntityNotification(tenantId, edgeNotificationMsg); - case TENANT_PROFILE -> future = edgeCtx.getTenantProfileProcessor().processEntityNotification(tenantId, edgeNotificationMsg); - case NOTIFICATION_RULE, NOTIFICATION_TARGET, NOTIFICATION_TEMPLATE -> - future = edgeCtx.getNotificationEdgeProcessor().processEntityNotification(tenantId, edgeNotificationMsg); - case TB_RESOURCE -> future = edgeCtx.getResourceProcessor().processEntityNotification(tenantId, edgeNotificationMsg); - case DOMAIN, OAUTH2_CLIENT -> future = edgeCtx.getOAuth2EdgeProcessor().processEntityNotification(tenantId, edgeNotificationMsg); - default -> { - future = Futures.immediateFuture(null); - log.warn("[{}] Edge event type [{}] is not designed to be pushed to edge", tenantId, type); - } - } + ListenableFuture future = edgeCtx.getProcessor(type).processEntityNotification(tenantId, edgeNotificationMsg); Futures.addCallback(future, new FutureCallback<>() { @Override public void onSuccess(@Nullable Void unused) { diff --git a/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbImageService.java b/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbImageService.java index 63fe2108f3..a1950b5068 100644 --- a/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbImageService.java +++ b/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbImageService.java @@ -126,7 +126,7 @@ public class DefaultTbImageService extends AbstractTbEntityService implements Tb return savedImage; } catch (Exception e) { image.setData(null); - logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.TB_RESOURCE), image, actionType, user, e); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.TB_RESOURCE), new TbResourceInfo(image), actionType, user, e); throw e; } } diff --git a/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java b/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java index e3a79a8ba5..f5ab939635 100644 --- a/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java +++ b/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java @@ -67,7 +67,7 @@ public class DefaultTbResourceService extends AbstractTbEntityService implements private final AccessControlService accessControlService; @Override - public TbResource save(TbResource resource, SecurityUser user) throws ThingsboardException { + public TbResourceInfo save(TbResource resource, SecurityUser user) throws ThingsboardException { if (resource.getResourceType() == ResourceType.IMAGE) { throw new IllegalArgumentException("Image resource type is not supported"); } @@ -79,12 +79,11 @@ public class DefaultTbResourceService extends AbstractTbEntityService implements } else if (resource.getResourceKey() == null) { resource.setResourceKey(resource.getFileName()); } - TbResource savedResource = resourceService.saveResource(resource); + TbResourceInfo savedResource = new TbResourceInfo(resourceService.saveResource(resource)); logEntityActionService.logEntityAction(tenantId, savedResource.getId(), savedResource, actionType, user); return savedResource; } catch (Exception e) { - logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.TB_RESOURCE), - resource, actionType, user, e); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.TB_RESOURCE), new TbResourceInfo(resource), actionType, user, e); throw e; } } diff --git a/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java b/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java index 0de2cded00..20e6c81a7d 100644 --- a/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java +++ b/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java @@ -18,17 +18,26 @@ package org.thingsboard.server.service.resource; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.ResourceExportData; import org.thingsboard.server.common.data.TbResource; +import org.thingsboard.server.common.data.TbResourceInfo; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.lwm2m.LwM2mObject; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.widget.WidgetTypeDetails; -import org.thingsboard.server.service.entitiy.SimpleTbEntityService; import org.thingsboard.server.service.security.model.SecurityUser; import java.util.List; -public interface TbResourceService extends SimpleTbEntityService { +public interface TbResourceService { + + default TbResourceInfo save(TbResource entity) throws Exception { + return save(entity, null); + } + + TbResourceInfo save(TbResource entity, SecurityUser user) throws Exception; + + void delete(TbResource entity, User user); List findLwM2mObject(TenantId tenantId, String sortOrder, diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java index debc1cb987..27a64dc443 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java @@ -115,8 +115,9 @@ public class DefaultTbRuleEngineRpcService implements TbRuleEngineDeviceRpcServi ToDeviceRpcRequest request = new ToDeviceRpcRequest(src.getRequestUUID(), src.getTenantId(), src.getDeviceId(), src.isOneway(), src.getExpirationTime(), new ToDeviceRpcRequestBody(src.getMethod(), src.getBody()), src.isPersisted(), src.getRetries(), src.getAdditionalInfo()); forwardRpcRequestToDeviceActor(request, response -> { - if (src.isRestApiCall()) { - sendRpcResponseToTbCore(src.getOriginServiceId(), response); + String originServiceId = src.getOriginServiceId(); + if (src.isRestApiCall() && originServiceId != null) { + sendRpcResponseToTbCore(originServiceId, response); } consumer.accept(RuleEngineDeviceRpcResponse.builder() .deviceId(src.getDeviceId()) diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java index d4444d5d7f..cab2f18dc7 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java @@ -118,10 +118,10 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer EntityId entityId = request.getEntityId(); checkInternalEntity(entityId); boolean sysTenant = TenantId.SYS_TENANT_ID.equals(tenantId) || tenantId == null; - if (sysTenant || request.isOnlyLatest() || apiUsageStateService.getApiUsageState(tenantId).isDbStorageEnabled()) { + if (sysTenant || !request.getStrategy().saveTimeseries() || apiUsageStateService.getApiUsageState(tenantId).isDbStorageEnabled()) { KvUtils.validate(request.getEntries(), valueNoXssValidation); ListenableFuture future = saveTimeseriesInternal(request); - if (!request.isOnlyLatest()) { + if (request.getStrategy().saveTimeseries()) { FutureCallback callback = getApiUsageCallback(tenantId, request.getCustomerId(), sysTenant, request.getCallback()); Futures.addCallback(future, callback, tsCallBackExecutor); } @@ -134,19 +134,24 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer public ListenableFuture saveTimeseriesInternal(TimeseriesSaveRequest request) { TenantId tenantId = request.getTenantId(); EntityId entityId = request.getEntityId(); + TimeseriesSaveRequest.Strategy strategy = request.getStrategy(); ListenableFuture saveFuture; - if (request.isOnlyLatest()) { - saveFuture = Futures.transform(tsService.saveLatest(tenantId, entityId, request.getEntries()), result -> 0, MoreExecutors.directExecutor()); - } else if (request.isSaveLatest()) { + if (strategy.saveTimeseries() && strategy.saveLatest()) { saveFuture = tsService.save(tenantId, entityId, request.getEntries(), request.getTtl()); - } else { + } else if (strategy.saveLatest()) { + saveFuture = Futures.transform(tsService.saveLatest(tenantId, entityId, request.getEntries()), result -> 0, MoreExecutors.directExecutor()); + } else if (strategy.saveTimeseries()) { saveFuture = tsService.saveWithoutLatest(tenantId, entityId, request.getEntries(), request.getTtl()); + } else { + saveFuture = Futures.immediateFuture(0); } addMainCallback(saveFuture, request.getCallback()); - addWsCallback(saveFuture, success -> onTimeSeriesUpdate(tenantId, entityId, request.getEntries())); - if (request.isSaveLatest() && !request.isOnlyLatest()) { - addEntityViewCallback(tenantId, entityId, request.getEntries()); + if (strategy.sendWsUpdate()) { + addWsCallback(saveFuture, success -> onTimeSeriesUpdate(tenantId, entityId, request.getEntries())); + } + if (strategy.saveLatest()) { + copyLatestToEntityViews(tenantId, entityId, request.getEntries()); } return saveFuture; } @@ -201,7 +206,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer } } - private void addEntityViewCallback(TenantId tenantId, EntityId entityId, List ts) { + private void copyLatestToEntityViews(TenantId tenantId, EntityId entityId, List ts) { if (EntityType.DEVICE.equals(entityId.getEntityType()) || EntityType.ASSET.equals(entityId.getEntityType())) { Futures.addCallback(this.tbEntityViewService.findEntityViewsByTenantIdAndEntityIdAsync(tenantId, entityId), new FutureCallback<>() { @@ -232,7 +237,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer .tenantId(tenantId) .entityId(entityView.getId()) .entries(entityViewLatest) - .onlyLatest(true) + .strategy(TimeseriesSaveRequest.Strategy.LATEST_AND_WS) .callback(new FutureCallback<>() { @Override public void onSuccess(@Nullable Void tmp) {} diff --git a/application/src/main/java/org/thingsboard/server/service/update/DeprecationService.java b/application/src/main/java/org/thingsboard/server/service/update/DeprecationService.java deleted file mode 100644 index ff1f013953..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/update/DeprecationService.java +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.update; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; -import org.thingsboard.rule.engine.api.NotificationCenter; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.notification.info.GeneralNotificationInfo; -import org.thingsboard.server.common.data.notification.targets.platform.SystemAdministratorsFilter; -import org.thingsboard.server.dao.notification.DefaultNotifications; -import org.thingsboard.server.dao.notification.DefaultNotifications.DefaultNotification; -import org.thingsboard.server.queue.util.AfterStartUp; - -import java.util.Map; - -@Service -@Slf4j -@RequiredArgsConstructor -public class DeprecationService { - - private final NotificationCenter notificationCenter; - - @Value("${queue.type}") - private String queueType; - - @Value("${database.ts.type}") - private String tsType; - - @Value("${database.ts_latest.type}") - private String tsLatestType; - - @AfterStartUp(order = Integer.MAX_VALUE) - public void checkDeprecation() { - checkQueueTypeDeprecation(); - checkDatabaseTypeDeprecation(); - } - - private void checkQueueTypeDeprecation() { - String queueTypeName; - switch (queueType) { - case "aws-sqs" -> queueTypeName = "AWS SQS"; - case "pubsub" -> queueTypeName = "PubSub"; - case "service-bus" -> queueTypeName = "Azure Service Bus"; - case "rabbitmq" -> queueTypeName = "RabbitMQ"; - default -> { - return; - } - } - - log.warn("WARNING: Starting with ThingsBoard 4.0, {} will no longer be supported as a message queue for microservices. " + - "Please migrate to Apache Kafka. This change will not impact any rule nodes", queueTypeName); - sendNotification(DefaultNotifications.queueTypeDeprecation, Map.of( - "queueType", queueTypeName - )); - } - - private void checkDatabaseTypeDeprecation() { - String deprecatedDatabaseType = "Timescale"; - if (StringUtils.equalsAnyIgnoreCase(deprecatedDatabaseType, tsType, tsLatestType)) { - log.warn("WARNING: Starting with ThingsBoard 4.0, {} will no longer be supported as a storage provider. " + - "Please migrate to Cassandra or PostgreSQL.", deprecatedDatabaseType); - sendNotification(DefaultNotifications.databaseTypeDeprecation, Map.of( - "databaseType", deprecatedDatabaseType - )); - } - } - - private void sendNotification(DefaultNotification notification, Map info) { - notificationCenter.sendGeneralWebNotification(TenantId.SYS_TENANT_ID, new SystemAdministratorsFilter(), - notification.toTemplate(), new GeneralNotificationInfo(info)); - } - -} diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index f29cc55197..a88ace58ae 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -212,9 +212,9 @@ ui: database: ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by a single API call to fetch telemetry records ts: - type: "${DATABASE_TS_TYPE:sql}" # cassandra or sql. timescale option is deprecated and will no longer be supported in ThingsBoard 4.0 + type: "${DATABASE_TS_TYPE:sql}" # cassandra, sql, or timescale (for hybrid mode, DATABASE_TS_TYPE value should be cassandra, or timescale) ts_latest: - type: "${DATABASE_TS_LATEST_TYPE:sql}" # cassandra or sql. timescale option is deprecated and will no longer be supported in ThingsBoard 4.0 + type: "${DATABASE_TS_LATEST_TYPE:sql}" # cassandra, sql, or timescale (for hybrid mode, DATABASE_TS_TYPE value should be cassandra, or timescale) # Cassandra driver configuration parameters cassandra: @@ -600,7 +600,7 @@ cache: maxSize: "${CACHE_SPECS_EDGE_SESSIONS_MAX_SIZE:10000}" # 0 means the cache is disabled relatedEdges: timeToLiveInMinutes: "${CACHE_SPECS_RELATED_EDGES_TTL:1440}" # Related Edges cache TTL - maxSize: "${CACHE_SPECS_RELATED_EDGES_MAX_SIZE:0}" # 0 means the cache is disabled + maxSize: "${CACHE_SPECS_RELATED_EDGES_MAX_SIZE:10000}" # 0 means the cache is disabled repositorySettings: timeToLiveInMinutes: "${CACHE_SPECS_REPOSITORY_SETTINGS_TTL:1440}" # Repository settings cache TTL maxSize: "${CACHE_SPECS_REPOSITORY_SETTINGS_MAX_SIZE:10000}" # 0 means the cache is disabled @@ -1242,8 +1242,10 @@ transport: snmp: # Enable/disable SNMP transport protocol enabled: "${SNMP_ENABLED:true}" - # Snmp bind port - bind_port: "${SNMP_BIND_PORT:1620}" + # SNMP bind address + bind_address: "${SNMP_BIND_ADDRESS:0.0.0.0}" + # SNMP bind port. Zero (random) by default. When using SNMP TRAPs - make sure to specify some static value, e.g. 1620 + bind_port: "${SNMP_BIND_PORT:0}" response_processing: # parallelism level for executor (workStealingPool) that is responsible for handling responses from SNMP devices parallelism_level: "${SNMP_RESPONSE_PROCESSING_PARALLELISM_LEVEL:4}" @@ -1271,7 +1273,7 @@ transport: # URL of gateways dashboard repository repository_url: "${TB_GATEWAY_DASHBOARD_SYNC_REPOSITORY_URL:https://github.com/thingsboard/gateway-management-extensions-dist.git}" # Branch of gateways dashboard repository to work with - branch: "${TB_GATEWAY_DASHBOARD_SYNC_BRANCH:main}" + branch: "${TB_GATEWAY_DASHBOARD_SYNC_BRANCH:}" # Fetch frequency in hours for gateways dashboard repository fetch_frequency: "${TB_GATEWAY_DASHBOARD_SYNC_FETCH_FREQUENCY:24}" diff --git a/application/src/test/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessorTest.java b/application/src/test/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessorTest.java index 35f53587e5..4f571d4fe6 100644 --- a/application/src/test/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessorTest.java +++ b/application/src/test/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessorTest.java @@ -17,22 +17,27 @@ package org.thingsboard.server.actors.device; import org.junit.Before; import org.junit.Test; +import org.mockito.Mockito; import org.thingsboard.common.util.LinkedHashMapRemoveEldest; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.service.transport.TbCoreToTransportService; + +import java.util.UUID; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.BDDMockito.willReturn; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.mock; public class DeviceActorMessageProcessorTest { - public static final long MAX_CONCURRENT_SESSIONS_PER_DEVICE = 10L; + public static final int MAX_CONCURRENT_SESSIONS_PER_DEVICE = 10; ActorSystemContext systemContext; DeviceService deviceService; TenantId tenantId = TenantId.SYS_TENANT_ID; @@ -44,15 +49,38 @@ public class DeviceActorMessageProcessorTest { public void setUp() { systemContext = mock(ActorSystemContext.class); deviceService = mock(DeviceService.class); - willReturn(MAX_CONCURRENT_SESSIONS_PER_DEVICE).given(systemContext).getMaxConcurrentSessionsPerDevice(); + willReturn((long)MAX_CONCURRENT_SESSIONS_PER_DEVICE).given(systemContext).getMaxConcurrentSessionsPerDevice(); willReturn(deviceService).given(systemContext).getDeviceService(); processor = new DeviceActorMessageProcessor(systemContext, tenantId, deviceId); + willReturn(mock(TbCoreToTransportService.class)).given(systemContext).getTbCoreToTransportService(); } @Test public void givenSystemContext_whenNewInstance_thenVerifySessionMapMaxSize() { assertThat(processor.sessions, instanceOf(LinkedHashMapRemoveEldest.class)); - assertThat(processor.sessions.getMaxEntries(), is(MAX_CONCURRENT_SESSIONS_PER_DEVICE)); + assertThat(processor.sessions.getMaxEntries(), is((long)MAX_CONCURRENT_SESSIONS_PER_DEVICE)); assertThat(processor.sessions.getRemovalConsumer(), notNullValue()); } + + @Test + public void givenFullSessionMap_whenSessionOverflow_thenShouldDeleteAttributeAndRPCSubscriptions() { + //givenFullSessionMap + for (int i = 0; i < MAX_CONCURRENT_SESSIONS_PER_DEVICE; i++) { + UUID sessionID = UUID.randomUUID(); + processor.sessions.put(sessionID, Mockito.mock(SessionInfoMetaData.class, RETURNS_DEEP_STUBS)); + processor.attributeSubscriptions.put(sessionID, Mockito.mock(SessionInfo.class)); + processor.rpcSubscriptions.put(sessionID, Mockito.mock(SessionInfo.class)); + } + assertThat(processor.sessions.size(), is(MAX_CONCURRENT_SESSIONS_PER_DEVICE)); + assertThat(processor.attributeSubscriptions.size(), is(MAX_CONCURRENT_SESSIONS_PER_DEVICE)); + assertThat(processor.rpcSubscriptions.size(), is(MAX_CONCURRENT_SESSIONS_PER_DEVICE)); + + //add one more + processor.sessions.put(UUID.randomUUID(), Mockito.mock(SessionInfoMetaData.class)); + + assertThat(processor.sessions.size(), is(MAX_CONCURRENT_SESSIONS_PER_DEVICE)); + assertThat(processor.attributeSubscriptions.size(), is(MAX_CONCURRENT_SESSIONS_PER_DEVICE-1)); + assertThat(processor.rpcSubscriptions.size(), is(MAX_CONCURRENT_SESSIONS_PER_DEVICE-1)); + + } } \ No newline at end of file diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java index 238203cdb0..f0e8832116 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java @@ -629,8 +629,11 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest { private String entityClassToEntityTypeName(HasName entity) { String entityType = entityClassToString(entity); - return "SAVE_OTA_PACKAGE_INFO_REQUEST".equals(entityType) || "OTA_PACKAGE_INFO".equals(entityType) ? - EntityType.OTA_PACKAGE.name().toUpperCase(Locale.ENGLISH) : entityType; + return switch (entityType) { + case "SAVE_OTA_PACKAGE_INFO_REQUEST", "OTA_PACKAGE_INFO" -> "OTA_PACKAGE"; + case "TB_RESOURCE_INFO" -> "TB_RESOURCE"; + default -> entityType; + }; } private String entityClassToString(HasName entity) { diff --git a/application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java index 4d38934dcb..0ffdbe8e60 100644 --- a/application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java @@ -897,7 +897,7 @@ public class EdgeControllerTest extends AbstractControllerTest { edgeImitator.ignoreType(OAuth2ClientUpdateMsg.class); edgeImitator.ignoreType(OAuth2DomainUpdateMsg.class); - edgeImitator.expectMessageAmount(26); + edgeImitator.expectMessageAmount(27); edgeImitator.connect(); waitForMessages(edgeImitator); @@ -1003,6 +1003,7 @@ public class EdgeControllerTest extends AbstractControllerTest { Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "connectivity")); Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "jwt")); Assert.assertTrue(popDeviceProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default")); + Assert.assertTrue(popDeviceProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default")); Assert.assertTrue(popAssetProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default")); Assert.assertTrue(popDeviceProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default")); Assert.assertTrue(popAssetProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default")); diff --git a/application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java index 23f5eadab7..9f2baaa1cd 100644 --- a/application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java @@ -104,7 +104,7 @@ public class TbResourceControllerTest extends AbstractControllerTest { resource.setFileName(DEFAULT_FILE_NAME); resource.setEncodedData(TEST_DATA); - TbResource savedResource = save(resource); + TbResourceInfo savedResource = save(resource); testNotifyEntityAllOneTimeLogEntityActionEntityEqClass(savedResource, savedResource.getId(), savedResource.getId(), savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), @@ -119,15 +119,15 @@ public class TbResourceControllerTest extends AbstractControllerTest { Assert.assertEquals(DEFAULT_FILE_NAME, savedResource.getResourceKey()); Assert.assertArrayEquals(resource.getData(), download(savedResource.getId())); - savedResource.setTitle("My new resource"); - savedResource.setData(null); + TbResource foundResource = doGet("/api/resource/" + savedResource.getId().getId().toString(), TbResource.class); + foundResource.setTitle("My new resource"); + foundResource.setData(null); - save(savedResource); + savedResource = save(foundResource); - TbResource foundResource = doGet("/api/resource/" + savedResource.getId().getId().toString(), TbResource.class); Assert.assertEquals(foundResource.getTitle(), savedResource.getTitle()); - testNotifyEntityAllOneTimeLogEntityActionEntityEqClass(foundResource, foundResource.getId(), foundResource.getId(), + testNotifyEntityAllOneTimeLogEntityActionEntityEqClass(savedResource, savedResource.getId(), savedResource.getId(), savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.UPDATED, ActionType.UPDATED); } @@ -147,7 +147,7 @@ public class TbResourceControllerTest extends AbstractControllerTest { .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString(msgError))); - testNotifyEntityEqualsOneTimeServiceNeverError(resource, savedTenant.getId(), + testNotifyEntityEqualsOneTimeServiceNeverError(new TbResourceInfo(resource), savedTenant.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); } @@ -159,7 +159,7 @@ public class TbResourceControllerTest extends AbstractControllerTest { resource.setFileName(DEFAULT_FILE_NAME); resource.setEncodedData(TEST_DATA); - TbResource savedResource = save(resource); + TbResourceInfo savedResource = save(resource); loginDifferentTenant(); @@ -188,7 +188,7 @@ public class TbResourceControllerTest extends AbstractControllerTest { resource.setFileName(DEFAULT_FILE_NAME); resource.setEncodedData(TEST_DATA); - TbResource savedResource = save(resource); + TbResourceInfo savedResource = save(resource); TbResource foundResource = doGet("/api/resource/" + savedResource.getUuidId(), TbResource.class); Assert.assertNotNull(foundResource); @@ -204,7 +204,7 @@ public class TbResourceControllerTest extends AbstractControllerTest { resource.setFileName(DEFAULT_FILE_NAME); resource.setEncodedData(TEST_DATA); - TbResource savedResource = save(resource); + TbResourceInfo savedResource = save(resource); Mockito.reset(tbClusterService, auditLogService); String resourceIdStr = savedResource.getId().getId().toString(); @@ -229,7 +229,7 @@ public class TbResourceControllerTest extends AbstractControllerTest { resource.setFileName(DEFAULT_FILE_NAME); resource.setEncodedData(TEST_DATA); - TbResource savedResource = save(resource); + TbResourceInfo savedResource = save(resource); Mockito.reset(tbClusterService, auditLogService); String resourceIdStr = savedResource.getId().getId().toString(); @@ -274,7 +274,7 @@ public class TbResourceControllerTest extends AbstractControllerTest { } } while (pageData.hasNext()); - testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new TbResource(), new TbResource(), + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new TbResourceInfo(), new TbResourceInfo(), savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, cntEntity, cntEntity, cntEntity); @@ -322,7 +322,7 @@ public class TbResourceControllerTest extends AbstractControllerTest { } } while (pageData.hasNext()); - testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new TbResource(), new TbResource(), + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new TbResourceInfo(), new TbResourceInfo(), savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, jksCntEntity + lwm2mCntEntity, jksCntEntity + lwm2mCntEntity, jksCntEntity + lwm2mCntEntity); @@ -414,7 +414,7 @@ public class TbResourceControllerTest extends AbstractControllerTest { resource.setResourceType(ResourceType.PKCS_12); resource.setFileName(i + DEFAULT_FILE_NAME_2); resource.setEncodedData(TEST_DATA); - TbResource saved = save(resource); + TbResourceInfo saved = save(resource); lwm2mesources.add(saved); } @@ -536,7 +536,7 @@ public class TbResourceControllerTest extends AbstractControllerTest { resource.setFileName(JS_TEST_FILE_NAME); resource.setEncodedData(TEST_DATA); - TbResource savedResource = save(resource); + TbResourceInfo savedResource = save(resource); testNotifyEntityAllOneTimeLogEntityActionEntityEqClass(savedResource, savedResource.getId(), savedResource.getId(), savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), @@ -567,7 +567,7 @@ public class TbResourceControllerTest extends AbstractControllerTest { resource.setFileName(JS_TEST_FILE_NAME); resource.setEncodedData(TEST_DATA); - TbResource savedResource = save(resource); + TbResourceInfo savedResource = save(resource); //download as public customer Device device = new Device(); @@ -611,7 +611,7 @@ public class TbResourceControllerTest extends AbstractControllerTest { resource.setFileName(JS_TEST_FILE_NAME); resource.setEncodedData(TEST_DATA); - TbResource savedResource = save(resource); + TbResourceInfo savedResource = save(resource); loginDifferentTenant(); loginDifferentTenantCustomer(); @@ -626,17 +626,19 @@ public class TbResourceControllerTest extends AbstractControllerTest { resource.setTitle("My resource"); resource.setFileName("3.pks"); resource.setEncodedData(TEST_DATA); - TbResource savedResource = save(resource); + TbResourceInfo savedResource = save(resource); resource.setEtag(savedResource.getEtag()); - savedResource.setEncodedData(TEST_DATA); - doPost("/api/resource", savedResource) + TbResource foundResource = doGet("/api/resource/" + savedResource.getUuidId(), TbResource.class); + + foundResource.setEncodedData(TEST_DATA); + doPost("/api/resource", foundResource) .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString("can't be updated"))); - savedResource.setData(null); - savedResource.setTitle("Updated resource"); - savedResource = doPost("/api/resource", savedResource, TbResource.class); + foundResource.setData(null); + foundResource.setTitle("Updated resource"); + savedResource = doPost("/api/resource", foundResource, TbResource.class); assertThat(savedResource.getTitle()).isEqualTo("Updated resource"); assertThat(savedResource.getFileName()).isEqualTo(resource.getFileName()); assertThat(savedResource.getEtag()).isEqualTo(resource.getEtag()); @@ -650,14 +652,16 @@ public class TbResourceControllerTest extends AbstractControllerTest { resource.setTitle("My resource"); resource.setFileName("module.js"); resource.setEncodedData(TEST_DATA); - TbResource savedResource = save(resource); - resource.setEtag(savedResource.getEtag()); + TbResourceInfo savedResource = save(resource); + + TbResource foundResource = doGet("/api/resource/" + savedResource.getUuidId(), TbResource.class); + resource.setEtag(foundResource.getEtag()); String newData = Base64.getEncoder().encodeToString(new byte[]{1, 2, 3}); - savedResource.setEncodedData(newData); - savedResource.setFileName("new-module.js"); - savedResource.setTitle("Updated title"); - savedResource = save(savedResource); + foundResource.setEncodedData(newData); + foundResource.setFileName("new-module.js"); + foundResource.setTitle("Updated title"); + savedResource = save(foundResource); assertThat(savedResource.getTitle()).isEqualTo("Updated title"); assertThat(savedResource.getFileName()).isEqualTo("new-module.js"); @@ -669,7 +673,7 @@ public class TbResourceControllerTest extends AbstractControllerTest { public void testGetLwm2mListObjectsPage() throws Exception { loginTenantAdmin(); - List resources = loadLwm2mResources(); + List resources = loadLwm2mResources(); List objects = doGetTyped("/api/resource/lwm2m/page?pageSize=100&page=0", new TypeReference<>() {}); @@ -683,7 +687,7 @@ public class TbResourceControllerTest extends AbstractControllerTest { public void testGetLwm2mListObjects() throws Exception { loginTenantAdmin(); - List resources = loadLwm2mResources(); + List resources = loadLwm2mResources(); List objects = doGetTyped("/api/resource/lwm2m?sortProperty=id&sortOrder=ASC&objectIds=3_1.2,5_1.2,19_1.1", new TypeReference<>() {}); @@ -693,7 +697,7 @@ public class TbResourceControllerTest extends AbstractControllerTest { removeLoadResources(resources); } - private TbResource save(TbResource tbResource) throws Exception { + private TbResourceInfo save(TbResource tbResource) throws Exception { return doPostWithTypedResponse("/api/resource", tbResource, new TypeReference<>() { }); } @@ -709,10 +713,10 @@ public class TbResourceControllerTest extends AbstractControllerTest { } - private List loadLwm2mResources() throws Exception { + private List loadLwm2mResources() throws Exception { var models = List.of("1", "2", "3", "5", "6", "9", "19", "3303"); - List resources = new ArrayList<>(models.size()); + List resources = new ArrayList<>(models.size()); for (String model : models) { String fileName = model + ".xml"; @@ -728,7 +732,7 @@ public class TbResourceControllerTest extends AbstractControllerTest { return resources; } - private void removeLoadResources(List resources) throws Exception { + private void removeLoadResources(List resources) throws Exception { for (TbResourceInfo resource : resources) { doDelete("/api/resource/" + resource.getId().getId().toString()) .andExpect(status().isOk()); diff --git a/application/src/test/java/org/thingsboard/server/controller/WidgetsBundleControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/WidgetsBundleControllerTest.java index 03506af95c..ee2fe1dae0 100644 --- a/application/src/test/java/org/thingsboard/server/controller/WidgetsBundleControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/WidgetsBundleControllerTest.java @@ -95,7 +95,7 @@ public class WidgetsBundleControllerTest extends AbstractControllerTest { Assert.assertEquals(widgetsBundle.getTitle(), savedWidgetsBundle.getTitle()); savedWidgetsBundle.setTitle("My new widgets bundle"); - doPost("/api/widgetsBundle", savedWidgetsBundle, WidgetsBundle.class); + savedWidgetsBundle = doPost("/api/widgetsBundle", savedWidgetsBundle, WidgetsBundle.class); WidgetsBundle foundWidgetsBundle = doGet("/api/widgetsBundle/" + savedWidgetsBundle.getId().getId().toString(), WidgetsBundle.class); Assert.assertEquals(foundWidgetsBundle.getTitle(), savedWidgetsBundle.getTitle()); diff --git a/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java index 10db03a6c2..afc0f72081 100644 --- a/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java @@ -144,7 +144,7 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { edgeImitator = new EdgeImitator("localhost", 7070, edge.getRoutingKey(), edge.getSecret()); edgeImitator.ignoreType(OAuth2ClientUpdateMsg.class); edgeImitator.ignoreType(OAuth2DomainUpdateMsg.class); - edgeImitator.expectMessageAmount(24); + edgeImitator.expectMessageAmount(26); edgeImitator.connect(); requestEdgeRuleChainMetadata(); @@ -265,10 +265,10 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { // 4 messages // - 1 from default profile fetcher - // - 2 from device profile fetcher (default and thermostat) + // - 4 from device profile fetcher (2 * (default and thermostat) before and after ota packages fetcher // - 1 from device fetcher - validateMsgsCnt(DeviceProfileUpdateMsg.class, 4); - validateDeviceProfiles(4); + validateMsgsCnt(DeviceProfileUpdateMsg.class, 6); + validateDeviceProfiles(6); // 3 messages // - 1 from default profile fetcher @@ -656,7 +656,7 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { } protected RuleChainId createEdgeRuleChainAndAssignToEdge(String ruleChainName) throws Exception { - edgeImitator.expectMessageAmount(1); + edgeImitator.expectMessageAmount(2); RuleChain ruleChain = new RuleChain(); ruleChain.setName(ruleChainName); ruleChain.setType(RuleChainType.EDGE); diff --git a/application/src/test/java/org/thingsboard/server/edge/DashboardEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/DashboardEdgeTest.java index 3b252046ab..42b75b5a74 100644 --- a/application/src/test/java/org/thingsboard/server/edge/DashboardEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/DashboardEdgeTest.java @@ -16,6 +16,7 @@ package org.thingsboard.server.edge; import com.fasterxml.jackson.core.type.TypeReference; +import com.google.common.collect.Sets; import com.google.protobuf.AbstractMessage; import org.junit.Assert; import org.junit.Test; @@ -47,12 +48,14 @@ public class DashboardEdgeTest extends AbstractEdgeTest { private static final int MOBILE_ORDER = 5; private static final String IMAGE = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjQ4IiBoZWlnaHQ9IjQ4Ij48cGF0aCBkPSJNMTMuMjMgMTAuNTZWMTBjLTEuOTQgMC0zLjk5LjM5LTMuOTkgMi42NyAwIDEuMTYuNjEgMS45NSAxLjYzIDEuOTUuNzYgMCAxLjQzLS40NyAxLjg2LTEuMjIuNTItLjkzLjUtMS44LjUtMi44NG0yLjcgNi41M2MtLjE4LjE2LS40My4xNy0uNjMuMDYtLjg5LS43NC0xLjA1LTEuMDgtMS41NC0xLjc5LTEuNDcgMS41LTIuNTEgMS45NS00LjQyIDEuOTUtMi4yNSAwLTQuMDEtMS4zOS00LjAxLTQuMTcgMC0yLjE4IDEuMTctMy42NCAyLjg2LTQuMzggMS40Ni0uNjQgMy40OS0uNzYgNS4wNC0uOTNWNy41YzAtLjY2LjA1LTEuNDEtLjMzLTEuOTYtLjMyLS40OS0uOTUtLjctMS41LS43LTEuMDIgMC0xLjkzLjUzLTIuMTUgMS42MS0uMDUuMjQtLjI1LjQ4LS40Ny40OWwtMi42LS4yOGMtLjIyLS4wNS0uNDYtLjIyLS40LS41Ni42LTMuMTUgMy40NS00LjEgNi00LjEgMS4zIDAgMyAuMzUgNC4wMyAxLjMzQzE3LjExIDQuNTUgMTcgNi4xOCAxNyA3Ljk1djQuMTdjMCAxLjI1LjUgMS44MSAxIDIuNDguMTcuMjUuMjEuNTQgMCAuNzFsLTIuMDYgMS43OGgtLjAxIj48L3BhdGg+PHBhdGggZD0iTTIwLjE2IDE5LjU0QzE4IDIxLjE0IDE0LjgyIDIyIDEyLjEgMjJjLTMuODEgMC03LjI1LTEuNDEtOS44NS0zLjc2LS4yLS4xOC0uMDItLjQzLjI1LS4yOSAyLjc4IDEuNjMgNi4yNSAyLjYxIDkuODMgMi42MSAyLjQxIDAgNS4wNy0uNSA3LjUxLTEuNTMuMzctLjE2LjY2LjI0LjMyLjUxIj48L3BhdGg+PHBhdGggZD0iTTIxLjA3IDE4LjVjLS4yOC0uMzYtMS44NS0uMTctMi41Ny0uMDgtLjE5LjAyLS4yMi0uMTYtLjAzLS4zIDEuMjQtLjg4IDMuMjktLjYyIDMuNTMtLjMzLjI0LjMtLjA3IDIuMzUtMS4yNCAzLjMyLS4xOC4xNi0uMzUuMDctLjI2LS4xMS4yNi0uNjcuODUtMi4xNC41Ny0yLjV6Ij48L3BhdGg+PC9zdmc+"; + private static final String DASHBOARD_TITLE = "Edge Test Dashboard"; + @Test public void testDashboards() throws Exception { // create dashboard and assign to edge edgeImitator.expectMessageAmount(2); Dashboard dashboard = new Dashboard(); - dashboard.setTitle("Edge Test Dashboard"); + dashboard.setTitle(DASHBOARD_TITLE); dashboard.setMobileHide(true); dashboard.setImage(IMAGE); dashboard.setMobileOrder(MOBILE_ORDER); @@ -175,7 +178,11 @@ public class DashboardEdgeTest extends AbstractEdgeTest { @Test public void testSendDashboardToCloud() throws Exception { - Dashboard dashboard = buildDashboardForUplinkMsg(); + Customer customer = new Customer(); + customer.setTitle("Edge Customer"); + Customer savedCustomer = doPost("/api/customer", customer, Customer.class); + + Dashboard dashboard = buildDashboardForUplinkMsg(savedCustomer); UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); DashboardUpdateMsg.Builder dashboardUpdateMsgBuilder = DashboardUpdateMsg.newBuilder(); @@ -195,7 +202,27 @@ public class DashboardEdgeTest extends AbstractEdgeTest { Dashboard foundDashboard = doGet("/api/dashboard/" + dashboard.getUuidId(), Dashboard.class); Assert.assertNotNull(foundDashboard); - Assert.assertEquals("Edge Test Dashboard", foundDashboard.getName()); + Assert.assertEquals(DASHBOARD_TITLE, foundDashboard.getName()); + + PageData pageData = doGetTypedWithPageLink("/api/customer/" + savedCustomer.getId().toString() + "/dashboards?", + new TypeReference<>() {}, new PageLink(100)); + Assert.assertEquals(1, pageData.getData().size()); + Assert.assertEquals(DASHBOARD_TITLE, pageData.getData().get(0).getTitle()); + + dashboard.setTitle(DASHBOARD_TITLE + " Updated"); + dashboard.setAssignedCustomers(null); + dashboardUpdateMsgBuilder.setEntity(JacksonUtil.toString(dashboard)); + dashboardUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE); + uplinkMsgBuilder = UplinkMsg.newBuilder(); + uplinkMsgBuilder.addDashboardUpdateMsg(dashboardUpdateMsgBuilder.build()); + + edgeImitator.expectResponsesAmount(1); + edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); + + Assert.assertTrue(edgeImitator.waitForResponses()); + + foundDashboard = doGet("/api/dashboard/" + dashboard.getUuidId(), Dashboard.class); + Assert.assertEquals(DASHBOARD_TITLE + " Updated", foundDashboard.getName()); } @Test @@ -242,11 +269,12 @@ public class DashboardEdgeTest extends AbstractEdgeTest { return savedDashboard; } - private Dashboard buildDashboardForUplinkMsg() { + private Dashboard buildDashboardForUplinkMsg(Customer savedCustomer) { Dashboard dashboard = new Dashboard(); dashboard.setId(new DashboardId(UUID.randomUUID())); dashboard.setTenantId(tenantId); - dashboard.setTitle("Edge Test Dashboard"); + dashboard.setTitle(DASHBOARD_TITLE); + dashboard.setAssignedCustomers(Sets.newHashSet(new ShortCustomerInfo(savedCustomer.getId(), savedCustomer.getTitle(), savedCustomer.isPublic()))); return dashboard; } diff --git a/application/src/test/java/org/thingsboard/server/edge/DeviceProfileEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/DeviceProfileEdgeTest.java index a085f3a821..c0b4fbdd61 100644 --- a/application/src/test/java/org/thingsboard/server/edge/DeviceProfileEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/DeviceProfileEdgeTest.java @@ -126,6 +126,45 @@ public class DeviceProfileEdgeTest extends AbstractEdgeTest { unAssignFromEdgeAndDeleteDashboard(thermostatsDashboardId); } + @Test + public void testDeleteDeviceProfilesWhenEdgeIsOffline() throws Exception { + RuleChainId thermostatsRuleChainId = createEdgeRuleChainAndAssignToEdge("Thermostats Rule Chain"); + + // create device profile + DeviceProfile deviceProfile = this.createDeviceProfile("ONE_MORE_DEVICE_PROFILE", null); + deviceProfile.setDefaultEdgeRuleChainId(thermostatsRuleChainId); + extendDeviceProfileData(deviceProfile); + edgeImitator.expectMessageAmount(1); + deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg); + DeviceProfileUpdateMsg deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage; + DeviceProfile deviceProfileMsg = JacksonUtil.fromString(deviceProfileUpdateMsg.getEntity(), DeviceProfile.class, true); + Assert.assertNotNull(deviceProfileMsg); + Assert.assertEquals(deviceProfile, deviceProfileMsg); + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType()); + + // delete profile when edge is offline + edgeImitator.disconnect(); + doDelete("/api/deviceProfile/" + deviceProfile.getUuidId()) + .andExpect(status().isOk()); + edgeImitator.connect(); + // 27 sync message + // + 1 delete message + edgeImitator.expectMessageAmount(28); + Assert.assertTrue(edgeImitator.waitForMessages()); + + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg); + deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType()); + Assert.assertEquals(deviceProfile.getUuidId().getMostSignificantBits(), deviceProfileUpdateMsg.getIdMSB()); + Assert.assertEquals(deviceProfile.getUuidId().getLeastSignificantBits(), deviceProfileUpdateMsg.getIdLSB()); + + unAssignFromEdgeAndDeleteRuleChain(thermostatsRuleChainId); + } + @Test public void testDeviceProfiles_snmp() throws Exception { DeviceProfile deviceProfile = createDeviceProfileAndDoBasicAssert("SNMP", createSnmpDeviceProfileTransportConfiguration()); @@ -359,7 +398,7 @@ public class DeviceProfileEdgeTest extends AbstractEdgeTest { transportConfiguration.setBootstrapServerUpdateEnable(true); TelemetryMappingConfiguration observeAttrConfiguration = - JacksonUtil.fromString(AbstractLwM2MIntegrationTest.OBSERVE_ATTRIBUTES_WITH_PARAMS, TelemetryMappingConfiguration.class); + JacksonUtil.fromString(AbstractLwM2MIntegrationTest.TELEMETRY_WITHOUT_OBSERVE, TelemetryMappingConfiguration.class); transportConfiguration.setObserveAttr(observeAttrConfiguration); List bootstrap = new ArrayList<>(); @@ -449,4 +488,5 @@ public class DeviceProfileEdgeTest extends AbstractEdgeTest { deviceProfile.setProfileData(createProfileData()); return deviceProfile; } + } diff --git a/application/src/test/java/org/thingsboard/server/edge/ResourceEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/ResourceEdgeTest.java index c27489d207..cc5aa4da0e 100644 --- a/application/src/test/java/org/thingsboard/server/edge/ResourceEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/ResourceEdgeTest.java @@ -17,6 +17,7 @@ package org.thingsboard.server.edge; import com.datastax.oss.driver.api.core.uuid.Uuids; import com.google.protobuf.AbstractMessage; +import com.google.protobuf.InvalidProtocolBufferException; import org.junit.Assert; import org.junit.Test; import org.thingsboard.common.util.JacksonUtil; @@ -98,30 +99,23 @@ public class ResourceEdgeTest extends AbstractEdgeTest { public void testSendResourceToCloud() throws Exception { TbResource tbResource = createTbResource(); UUID uuid = Uuids.timeBased(); + UplinkMsg uplinkMsg = getUplinkMsg(uuid, tbResource, UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE); - UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); - ResourceUpdateMsg.Builder resourceUpdateMsgBuilder = ResourceUpdateMsg.newBuilder(); - resourceUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits()); - resourceUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits()); - resourceUpdateMsgBuilder.setEntity(JacksonUtil.toString(tbResource)); - resourceUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE); - testAutoGeneratedCodeByProtobuf(resourceUpdateMsgBuilder); - uplinkMsgBuilder.addResourceUpdateMsg(resourceUpdateMsgBuilder.build()); + checkResourceOnCloud(uplinkMsg, uuid, tbResource.getTitle()); + } - testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); + @Test + public void testUpdateResourceTitleOnCloud() throws Exception { + TbResource tbResource = createTbResource(); + UUID uuid = Uuids.timeBased(); + UplinkMsg uplinkMsg = getUplinkMsg(uuid, tbResource, UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE); - edgeImitator.expectResponsesAmount(1); - edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); + checkResourceOnCloud(uplinkMsg, uuid, tbResource.getTitle()); - Assert.assertTrue(edgeImitator.waitForResponses()); + tbResource.setTitle("Updated Edge Test Resource"); + UplinkMsg updatedUplinkMsg = getUplinkMsg(uuid, tbResource, UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE); - UplinkResponseMsg latestResponseMsg = edgeImitator.getLatestResponseMsg(); - Assert.assertTrue(latestResponseMsg.getSuccess()); - - TbResource tb = doGet("/api/resource/" + uuid, TbResource.class); - Assert.assertNotNull(tb); - Assert.assertEquals("Edge Test Resource", tb.getName()); - Assert.assertEquals(TEST_DATA, tb.getEncodedData()); + checkResourceOnCloud(updatedUplinkMsg, uuid, tbResource.getTitle()); } @Test @@ -134,21 +128,12 @@ public class ResourceEdgeTest extends AbstractEdgeTest { UUID uuid = Uuids.timeBased(); - UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); - ResourceUpdateMsg.Builder resourceUpdateMsgBuilder = ResourceUpdateMsg.newBuilder(); - resourceUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits()); - resourceUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits()); - resourceUpdateMsgBuilder.setEntity(JacksonUtil.toString(resource)); - resourceUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE); - testAutoGeneratedCodeByProtobuf(resourceUpdateMsgBuilder); - uplinkMsgBuilder.addResourceUpdateMsg(resourceUpdateMsgBuilder.build()); - - testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); + UplinkMsg uplinkMsg = getUplinkMsg(uuid, resource, UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE); edgeImitator.expectResponsesAmount(1); edgeImitator.expectMessageAmount(1); - edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); + edgeImitator.sendUplinkMsg(uplinkMsg); Assert.assertTrue(edgeImitator.waitForResponses()); Assert.assertTrue(edgeImitator.waitForMessages()); @@ -177,4 +162,35 @@ public class ResourceEdgeTest extends AbstractEdgeTest { tbResource.setEncodedData(TEST_DATA); return tbResource; } + + private UplinkMsg getUplinkMsg(UUID uuid, TbResource tbResource, UpdateMsgType updateMsgType) throws InvalidProtocolBufferException { + UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); + ResourceUpdateMsg.Builder resourceUpdateMsgBuilder = ResourceUpdateMsg.newBuilder(); + resourceUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits()); + resourceUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits()); + resourceUpdateMsgBuilder.setEntity(JacksonUtil.toString(tbResource)); + resourceUpdateMsgBuilder.setMsgType(updateMsgType); + testAutoGeneratedCodeByProtobuf(resourceUpdateMsgBuilder); + uplinkMsgBuilder.addResourceUpdateMsg(resourceUpdateMsgBuilder.build()); + + testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); + + return uplinkMsgBuilder.build(); + } + + private void checkResourceOnCloud(UplinkMsg uplinkMsg, UUID uuid, String resourceTitle) throws Exception { + edgeImitator.expectResponsesAmount(1); + edgeImitator.sendUplinkMsg(uplinkMsg); + + Assert.assertTrue(edgeImitator.waitForResponses()); + + UplinkResponseMsg latestResponseMsg = edgeImitator.getLatestResponseMsg(); + Assert.assertTrue(latestResponseMsg.getSuccess()); + + TbResource tb = doGet("/api/resource/" + uuid, TbResource.class); + Assert.assertNotNull(tb); + Assert.assertEquals(resourceTitle, tb.getName()); + Assert.assertEquals(TEST_DATA, tb.getEncodedData()); + } + } diff --git a/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java b/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java index 45dfed1bcb..890ceb373f 100644 --- a/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java +++ b/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java @@ -66,7 +66,9 @@ import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg; import java.lang.reflect.Field; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ThreadLocalRandom; @@ -78,6 +80,7 @@ import java.util.stream.Collectors; @Slf4j public class EdgeImitator { + private static final int MAX_DOWNLINK_FAILS = 2; private final String routingKey; private final String routingSecret; @@ -93,6 +96,7 @@ public class EdgeImitator { private boolean randomFailuresOnTimeseriesDownlink = false; @Setter private double failureProbability = 0.0; + private final Map downlinkFailureCountMap = new HashMap<>(); @Getter private EdgeConfiguration configuration; @@ -244,8 +248,11 @@ public class EdgeImitator { if (downlinkMsg.getEntityDataCount() > 0) { for (EntityDataProto entityData : downlinkMsg.getEntityDataList()) { if (randomFailuresOnTimeseriesDownlink) { - if (getRandomBoolean()) { + int downlinkMsgId = downlinkMsg.getDownlinkMsgId(); + + if (getRandomBoolean() && checkFailureThreshold(downlinkMsgId)) { result.add(Futures.immediateFailedFuture(new RuntimeException("Random failure. This is expected error for edge test"))); + downlinkFailureCountMap.put(downlinkMsgId, downlinkFailureCountMap.getOrDefault(downlinkMsgId, 0) + 1); } else { result.add(saveDownlinkMsg(entityData)); } @@ -354,6 +361,12 @@ public class EdgeImitator { return Futures.allAsList(result); } + private boolean checkFailureThreshold(int downlinkMsgId) { + return failureProbability == 100 || + downlinkFailureCountMap.get(downlinkMsgId) == null || + downlinkFailureCountMap.get(downlinkMsgId) < MAX_DOWNLINK_FAILS; + } + private boolean getRandomBoolean() { double randomValue = ThreadLocalRandom.current().nextDouble() * 100; return randomValue <= this.failureProbability; diff --git a/application/src/test/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewServiceTest.java b/application/src/test/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewServiceTest.java new file mode 100644 index 0000000000..aa6bfde935 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewServiceTest.java @@ -0,0 +1,107 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.entitiy.entityview; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.thingsboard.rule.engine.api.TimeseriesSaveRequest; +import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.EntityViewId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.kv.BasicTsKvEntry; +import org.thingsboard.server.common.data.kv.DoubleDataEntry; +import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.objects.AttributesEntityView; +import org.thingsboard.server.common.data.objects.TelemetryEntityView; +import org.thingsboard.server.dao.attributes.AttributesService; +import org.thingsboard.server.dao.entityview.EntityViewService; +import org.thingsboard.server.dao.timeseries.TimeseriesService; +import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; + +import java.util.List; +import java.util.UUID; + +import static com.google.common.util.concurrent.Futures.immediateFuture; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; + +@ExtendWith(MockitoExtension.class) +class DefaultTbEntityViewServiceTest { + + final TenantId tenantId = TenantId.fromUUID(UUID.fromString("f09c8180-686c-11ef-9471-a71d33080e9c")); + final EntityId entityId = DeviceId.fromString("782aaab0-c7a8-11ef-a668-79582e785d5f"); + + @Mock + EntityViewService entityViewService; + @Mock + AttributesService attributesService; + @Mock + TelemetrySubscriptionService tsSubService; + @Mock + TimeseriesService tsService; + + DefaultTbEntityViewService defaultTbEntityViewService; + + @BeforeEach + void setup() { + defaultTbEntityViewService = new DefaultTbEntityViewService(entityViewService, attributesService, tsSubService, tsService); + } + + @Test + void shouldNotSaveTimeseriesWhenCopyingLatestToEntityView() throws Exception { + // GIVEN + var entityView = new EntityView(new EntityViewId(UUID.randomUUID())); + entityView.setTenantId(tenantId); + entityView.setEntityId(entityId); + entityView.setKeys(new TelemetryEntityView(List.of("temperature"), new AttributesEntityView())); + + List latest = List.of(new BasicTsKvEntry(123L, new DoubleDataEntry("temperature", 22.3))); + + given(tsService.findAll(eq(tenantId), eq(entityId), anyList())).willReturn(immediateFuture(latest)); + + // WHEN + defaultTbEntityViewService.updateEntityViewAttributes(tenantId, entityView, null, null); + + // THEN + var captor = ArgumentCaptor.forClass(TimeseriesSaveRequest.class); + then(tsSubService).should().saveTimeseries(captor.capture()); + + var expectedCopyLatestRequest = TimeseriesSaveRequest.builder() + .tenantId(tenantId) + .entityId(entityView.getId()) + .entries(latest) + .ttl(0L) + .strategy(TimeseriesSaveRequest.Strategy.LATEST_AND_WS) + .build(); + + var actualCopyLatestRequest = captor.getValue(); + + assertThat(actualCopyLatestRequest) + .usingRecursiveComparison() + .ignoringFields("callback") + .isEqualTo(expectedCopyLatestRequest); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java b/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java index d7c4a68195..405423dd03 100644 --- a/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java @@ -202,7 +202,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { } } - private TbResource createResource(String title, String filename) throws Exception { + private TbResourceInfo createResource(String title, String filename) throws Exception { TbResource resource = new TbResource(); resource.setTenantId(tenantId); resource.setTitle(title); @@ -222,25 +222,29 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setFileName(DEFAULT_FILE_NAME); resource.setData(TEST_DATA); - TbResource savedResource = tbResourceService.save(resource); + TbResourceInfo savedResource = tbResourceService.save(resource); - Assert.assertNotNull(savedResource); - Assert.assertNotNull(savedResource.getId()); - Assert.assertTrue(savedResource.getCreatedTime() > 0); - assertEquals(resource.getTenantId(), savedResource.getTenantId()); - assertEquals(resource.getTitle(), savedResource.getTitle()); - assertEquals(resource.getResourceKey(), savedResource.getResourceKey()); - assertArrayEquals(TEST_DATA, savedResource.getData()); + TbResource foundResource = resourceService.findResourceById(tenantId, savedResource.getId()); - savedResource.setTitle("My new resource"); - savedResource.setData(null); + Assert.assertNotNull(foundResource); + Assert.assertNotNull(foundResource.getId()); + Assert.assertTrue(foundResource.getCreatedTime() > 0); + assertEquals(resource.getTenantId(), foundResource.getTenantId()); + assertEquals(resource.getTitle(), foundResource.getTitle()); + assertEquals(resource.getResourceKey(), foundResource.getResourceKey()); + assertArrayEquals(TEST_DATA, foundResource.getData()); - tbResourceService.save(savedResource); - TbResource foundResource = resourceService.findResourceById(tenantId, savedResource.getId()); - assertEquals(foundResource.getTitle(), savedResource.getTitle()); + String title = "My new resource"; + + foundResource.setTitle(title); + foundResource.setData(null); + + tbResourceService.save(foundResource); + foundResource = resourceService.findResourceById(tenantId, foundResource.getId()); + assertEquals(title, foundResource.getTitle()); assertArrayEquals(foundResource.getData(), TEST_DATA); - tbResourceService.delete(savedResource, null); + tbResourceService.delete(foundResource, null); } @Test @@ -251,17 +255,19 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setFileName("test_model.xml"); resource.setEncodedData(Base64.getEncoder().encodeToString(LWM2M_TEST_MODEL.getBytes())); - TbResource savedResource = tbResourceService.save(resource); + TbResourceInfo savedResource = tbResourceService.save(resource); - Assert.assertNotNull(savedResource); - Assert.assertNotNull(savedResource.getId()); - Assert.assertTrue(savedResource.getCreatedTime() > 0); - assertEquals(resource.getTenantId(), savedResource.getTenantId()); - assertEquals("My first resource id=0 v1.0", savedResource.getTitle()); - assertEquals("0_1.0", savedResource.getResourceKey()); - assertArrayEquals(savedResource.getData(), LWM2M_TEST_MODEL.getBytes()); + TbResource foundResource = resourceService.findResourceById(tenantId, savedResource.getId()); - tbResourceService.delete(savedResource, null); + Assert.assertNotNull(foundResource); + Assert.assertNotNull(foundResource.getId()); + Assert.assertTrue(foundResource.getCreatedTime() > 0); + assertEquals(resource.getTenantId(), foundResource.getTenantId()); + assertEquals("My first resource id=0 v1.0", foundResource.getTitle()); + assertEquals("0_1.0", foundResource.getResourceKey()); + assertArrayEquals(foundResource.getData(), LWM2M_TEST_MODEL.getBytes()); + + tbResourceService.delete(foundResource, null); } @Test @@ -271,11 +277,12 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setTitle("My resource"); resource.setFileName(DEFAULT_FILE_NAME); resource.setData(TEST_DATA); - TbResource savedResource = tbResourceService.save(resource); + TbResourceInfo savedResource = tbResourceService.save(resource); assertEquals(TenantId.SYS_TENANT_ID, savedResource.getTenantId()); - tbResourceService.delete(savedResource, null); + TbResource foundResource = resourceService.findResourceById(tenantId, savedResource.getId()); + tbResourceService.delete(foundResource, null); } @Test @@ -286,7 +293,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setTitle("My resource"); resource.setFileName(DEFAULT_FILE_NAME); resource.setData(TEST_DATA); - resource = tbResourceService.save(resource); + TbResourceInfo savedResource = tbResourceService.save(resource); TbResource resource2 = new TbResource(); resource2.setTenantId(tenantId); @@ -294,11 +301,11 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource2.setTitle("My resource"); resource2.setFileName(DEFAULT_FILE_NAME); resource2.setData(TEST_DATA); - resource2 = tbResourceService.save(resource2); + TbResourceInfo savedResource2 = tbResourceService.save(resource2); - assertThat(resource2.getId()).isNotEqualTo(resource.getId()); - assertThat(resource2.getFileName()).isEqualTo("test.jks"); - assertThat(resource2.getResourceKey()).isEqualTo("test_(1).jks"); + assertThat(savedResource2.getId()).isNotEqualTo(savedResource.getId()); + assertThat(savedResource2.getFileName()).isEqualTo("test.jks"); + assertThat(savedResource2.getResourceKey()).isEqualTo("test_(1).jks"); } @Test @@ -348,12 +355,13 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setTitle("My resource"); resource.setFileName(DEFAULT_FILE_NAME); resource.setData(TEST_DATA); - TbResource savedResource = tbResourceService.save(resource); + TbResourceInfo savedResource = tbResourceService.save(resource); TbResource foundResource = resourceService.findResourceById(tenantId, savedResource.getId()); Assert.assertNotNull(foundResource); - assertEquals(savedResource, foundResource); - tbResourceService.delete(savedResource, null); + assertEquals(savedResource, new TbResourceInfo(foundResource)); + assertArrayEquals(TEST_DATA, foundResource.getData()); + tbResourceService.delete(foundResource, null); } @Test @@ -364,12 +372,13 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setTitle("My resource"); resource.setFileName(DEFAULT_FILE_NAME); resource.setData(TEST_DATA); - TbResource savedResource = tbResourceService.save(resource); + TbResourceInfo savedResource = tbResourceService.save(resource); TbResource foundResource = resourceService.findResourceByTenantIdAndKey(tenantId, savedResource.getResourceType(), savedResource.getResourceKey()); Assert.assertNotNull(foundResource); - assertEquals(savedResource, foundResource); - tbResourceService.delete(savedResource, null); + assertEquals(savedResource, new TbResourceInfo(foundResource)); + assertArrayEquals(TEST_DATA, foundResource.getData()); + tbResourceService.delete(foundResource, null); } @Test @@ -379,11 +388,11 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setTitle("My resource"); resource.setFileName(DEFAULT_FILE_NAME); resource.setData(TEST_DATA); - TbResource savedResource = tbResourceService.save(resource); + TbResourceInfo savedResource = tbResourceService.save(resource); TbResource foundResource = resourceService.findResourceById(tenantId, savedResource.getId()); Assert.assertNotNull(foundResource); - tbResourceService.delete(savedResource, null); + tbResourceService.delete(foundResource, null); foundResource = resourceService.findResourceById(tenantId, savedResource.getId()); Assert.assertNull(foundResource); } @@ -471,7 +480,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setResourceType(ResourceType.JKS); resource.setFileName(i + DEFAULT_FILE_NAME); resource.setData(TEST_DATA); - resources.add(new TbResourceInfo(tbResourceService.save(resource))); + resources.add(tbResourceService.save(resource)); } List loadedResources = new ArrayList<>(); diff --git a/application/src/test/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionServiceTest.java b/application/src/test/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionServiceTest.java new file mode 100644 index 0000000000..10fdd85504 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionServiceTest.java @@ -0,0 +1,373 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.telemetry; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.common.util.concurrent.SettableFuture; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; +import org.thingsboard.rule.engine.api.TimeseriesSaveRequest; +import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.ApiUsageRecordKey; +import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.ApiUsageStateValue; +import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.EntityViewId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.kv.BasicTsKvEntry; +import org.thingsboard.server.common.data.kv.DoubleDataEntry; +import org.thingsboard.server.common.data.kv.KvEntry; +import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.objects.AttributesEntityView; +import org.thingsboard.server.common.data.objects.TelemetryEntityView; +import org.thingsboard.server.common.msg.queue.ServiceType; +import org.thingsboard.server.common.msg.queue.TbCallback; +import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; +import org.thingsboard.server.common.stats.TbApiUsageReportClient; +import org.thingsboard.server.dao.attributes.AttributesService; +import org.thingsboard.server.dao.timeseries.TimeseriesService; +import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.discovery.QueueKey; +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; +import org.thingsboard.server.service.apiusage.TbApiUsageStateService; +import org.thingsboard.server.service.entitiy.entityview.TbEntityViewService; +import org.thingsboard.server.service.subscription.SubscriptionManagerService; + +import java.time.Duration; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.stream.LongStream; +import java.util.stream.Stream; + +import static com.google.common.util.concurrent.Futures.immediateFuture; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.lenient; + +@ExtendWith(MockitoExtension.class) +class DefaultTelemetrySubscriptionServiceTest { + + final TenantId tenantId = TenantId.fromUUID(UUID.fromString("a00ec470-c6b4-11ef-8c88-63b5533fb5bc")); + final CustomerId customerId = new CustomerId(UUID.fromString("7bdc9750-c775-11ef-8e03-ff69ed8da327")); + final EntityId entityId = DeviceId.fromString("cc51e450-53e1-11ee-883e-e56b48fd2088"); + + final long sampleTtl = 10_000L; + + final List sampleTelemetry = List.of( + new BasicTsKvEntry(100L, new DoubleDataEntry("temperature", 65.2)), + new BasicTsKvEntry(100L, new DoubleDataEntry("humidity", 33.1)) + ); + + ApiUsageState apiUsageState; + + final TopicPartitionInfo tpi = TopicPartitionInfo.builder() + .tenantId(tenantId) + .myPartition(true) + .build(); + + final FutureCallback emptyCallback = new FutureCallback<>() { + @Override + public void onSuccess(Void result) {} + + @Override + public void onFailure(@NonNull Throwable t) {} + }; + + ExecutorService wsCallBackExecutor; + ExecutorService tsCallBackExecutor; + + @Mock + TbClusterService clusterService; + @Mock + PartitionService partitionService; + @Mock + SubscriptionManagerService subscriptionManagerService; + @Mock + AttributesService attrService; + @Mock + TimeseriesService tsService; + @Mock + TbEntityViewService tbEntityViewService; + @Mock + TbApiUsageReportClient apiUsageClient; + @Mock + TbApiUsageStateService apiUsageStateService; + + DefaultTelemetrySubscriptionService telemetryService; + + @BeforeEach + void setup() { + telemetryService = new DefaultTelemetrySubscriptionService(attrService, tsService, tbEntityViewService, apiUsageClient, apiUsageStateService); + ReflectionTestUtils.setField(telemetryService, "clusterService", clusterService); + ReflectionTestUtils.setField(telemetryService, "partitionService", partitionService); + ReflectionTestUtils.setField(telemetryService, "subscriptionManagerService", Optional.of(subscriptionManagerService)); + + wsCallBackExecutor = MoreExecutors.newDirectExecutorService(); + ReflectionTestUtils.setField(telemetryService, "wsCallBackExecutor", wsCallBackExecutor); + + tsCallBackExecutor = MoreExecutors.newDirectExecutorService(); + ReflectionTestUtils.setField(telemetryService, "tsCallBackExecutor", tsCallBackExecutor); + + apiUsageState = new ApiUsageState(); + apiUsageState.setDbStorageState(ApiUsageStateValue.ENABLED); + lenient().when(apiUsageStateService.getApiUsageState(tenantId)).thenReturn(apiUsageState); + + lenient().when(partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId)).thenReturn(tpi); + + lenient().when(tsService.save(tenantId, entityId, sampleTelemetry, sampleTtl)).thenReturn(immediateFuture(sampleTelemetry.size())); + lenient().when(tsService.saveWithoutLatest(tenantId, entityId, sampleTelemetry, sampleTtl)).thenReturn(immediateFuture(sampleTelemetry.size())); + lenient().when(tsService.saveLatest(tenantId, entityId, sampleTelemetry)).thenReturn(immediateFuture(listOfNNumbers(sampleTelemetry.size()))); + + // mock no entity views + lenient().when(tbEntityViewService.findEntityViewsByTenantIdAndEntityIdAsync(tenantId, entityId)).thenReturn(immediateFuture(Collections.emptyList())); + + // send partition change event so currentPartitions set is populated + telemetryService.onTbApplicationEvent(new PartitionChangeEvent(this, ServiceType.TB_CORE, Map.of(new QueueKey(ServiceType.TB_CORE), Set.of(tpi)))); + } + + @AfterEach + void cleanup() { + wsCallBackExecutor.shutdownNow(); + tsCallBackExecutor.shutdownNow(); + } + + @Test + void shouldReportStorageDataPointsApiUsageWhenTimeSeriesIsSaved() { + // GIVEN + var request = TimeseriesSaveRequest.builder() + .tenantId(tenantId) + .customerId(customerId) + .entityId(entityId) + .entries(sampleTelemetry) + .ttl(sampleTtl) + .strategy(new TimeseriesSaveRequest.Strategy(true, false, false)) + .callback(emptyCallback) + .build(); + + // WHEN + telemetryService.saveTimeseries(request); + + // THEN + then(apiUsageClient).should().report(tenantId, customerId, ApiUsageRecordKey.STORAGE_DP_COUNT, sampleTelemetry.size()); + } + + @Test + void shouldNotReportStorageDataPointsApiUsageWhenTimeSeriesIsNotSaved() { + // GIVEN + var request = TimeseriesSaveRequest.builder() + .tenantId(tenantId) + .customerId(customerId) + .entityId(entityId) + .entries(sampleTelemetry) + .ttl(sampleTtl) + .strategy(TimeseriesSaveRequest.Strategy.LATEST_AND_WS) + .callback(emptyCallback) + .build(); + + // WHEN + telemetryService.saveTimeseries(request); + + // THEN + then(apiUsageClient).shouldHaveNoInteractions(); + } + + @Test + void shouldThrowStorageDisabledWhenTimeSeriesIsSavedAndStorageIsDisabled() { + // GIVEN + apiUsageState.setDbStorageState(ApiUsageStateValue.DISABLED); + + SettableFuture future = SettableFuture.create(); + var request = TimeseriesSaveRequest.builder() + .tenantId(tenantId) + .customerId(customerId) + .entityId(entityId) + .entries(sampleTelemetry) + .ttl(sampleTtl) + .strategy(TimeseriesSaveRequest.Strategy.SAVE_ALL) + .future(future) + .build(); + + // WHEN + telemetryService.saveTimeseries(request); + + // THEN + assertThat(future).failsWithin(Duration.ofSeconds(5)) + .withThrowableOfType(ExecutionException.class) + .withCauseInstanceOf(RuntimeException.class) + .withMessageContaining("DB storage writes are disabled due to API limits!"); + } + + @Test + void shouldNotThrowStorageDisabledWhenTimeSeriesIsNotSavedAndStorageIsDisabled() { + // GIVEN + apiUsageState.setDbStorageState(ApiUsageStateValue.DISABLED); + + SettableFuture future = SettableFuture.create(); + var request = TimeseriesSaveRequest.builder() + .tenantId(tenantId) + .customerId(customerId) + .entityId(entityId) + .entries(sampleTelemetry) + .ttl(sampleTtl) + .strategy(TimeseriesSaveRequest.Strategy.LATEST_AND_WS) + .future(future) + .build(); + + // WHEN + telemetryService.saveTimeseries(request); + + // THEN + assertThat(future).succeedsWithin(Duration.ofSeconds(5)); + } + + @Test + void shouldCopyLatestToEntityViewWhenLatestIsSavedOnMainEntity() { + // GIVEN + var entityView = new EntityView(new EntityViewId(UUID.randomUUID())); + entityView.setTenantId(tenantId); + entityView.setCustomerId(customerId); + entityView.setEntityId(entityId); + entityView.setKeys(new TelemetryEntityView(sampleTelemetry.stream().map(KvEntry::getKey).toList(), new AttributesEntityView())); + + // mock that there is one entity view + given(tbEntityViewService.findEntityViewsByTenantIdAndEntityIdAsync(tenantId, entityId)).willReturn(immediateFuture(List.of(entityView))); + // mock that save latest call for entity view is successful + given(tsService.saveLatest(tenantId, entityView.getId(), sampleTelemetry)).willReturn(immediateFuture(listOfNNumbers(sampleTelemetry.size()))); + // mock TPI for entity view + given(partitionService.resolve(ServiceType.TB_CORE, tenantId, entityView.getId())).willReturn(tpi); + + var request = TimeseriesSaveRequest.builder() + .tenantId(tenantId) + .customerId(customerId) + .entityId(entityId) + .entries(sampleTelemetry) + .ttl(sampleTtl) + .strategy(new TimeseriesSaveRequest.Strategy(false, true, false)) + .callback(emptyCallback) + .build(); + + // WHEN + telemetryService.saveTimeseries(request); + + // THEN + // should save latest to both the main entity and it's entity view + then(tsService).should().saveLatest(tenantId, entityId, sampleTelemetry); + then(tsService).should().saveLatest(tenantId, entityView.getId(), sampleTelemetry); + then(tsService).shouldHaveNoMoreInteractions(); + + // should send WS update only for entity view (WS update for the main entity is disabled in the save request) + then(subscriptionManagerService).should().onTimeSeriesUpdate(tenantId, entityView.getId(), sampleTelemetry, TbCallback.EMPTY); + then(subscriptionManagerService).shouldHaveNoMoreInteractions(); + } + + @Test + void shouldNotCopyLatestToEntityViewWhenLatestIsNotSavedOnMainEntity() { + // GIVEN + var request = TimeseriesSaveRequest.builder() + .tenantId(tenantId) + .customerId(customerId) + .entityId(entityId) + .entries(sampleTelemetry) + .ttl(sampleTtl) + .strategy(new TimeseriesSaveRequest.Strategy(true, false, false)) + .callback(emptyCallback) + .build(); + + // WHEN + telemetryService.saveTimeseries(request); + + // THEN + // should save only time series for the main entity + then(tsService).should().saveWithoutLatest(tenantId, entityId, sampleTelemetry, sampleTtl); + then(tsService).shouldHaveNoMoreInteractions(); + + // should not send any WS updates + then(subscriptionManagerService).shouldHaveNoInteractions(); + } + + @ParameterizedTest + @MethodSource("booleanCombinations") + void shouldCallCorrectApiBasedOnBooleanFlagsInTheSaveRequest(boolean saveTimeseries, boolean saveLatest, boolean sendWsUpdate) { + // GIVEN + var request = TimeseriesSaveRequest.builder() + .tenantId(tenantId) + .customerId(customerId) + .entityId(entityId) + .entries(sampleTelemetry) + .ttl(sampleTtl) + .strategy(new TimeseriesSaveRequest.Strategy(saveTimeseries, saveLatest, sendWsUpdate)) + .callback(emptyCallback) + .build(); + + // WHEN + telemetryService.saveTimeseries(request); + + // THEN + if (saveTimeseries && saveLatest) { + then(tsService).should().save(tenantId, entityId, sampleTelemetry, sampleTtl); + } else if (saveLatest) { + then(tsService).should().saveLatest(tenantId, entityId, sampleTelemetry); + } else if (saveTimeseries) { + then(tsService).should().saveWithoutLatest(tenantId, entityId, sampleTelemetry, sampleTtl); + } + then(tsService).shouldHaveNoMoreInteractions(); + + if (sendWsUpdate) { + then(subscriptionManagerService).should().onTimeSeriesUpdate(tenantId, entityId, sampleTelemetry, TbCallback.EMPTY); + } else { + then(subscriptionManagerService).shouldHaveNoInteractions(); + } + } + + private static Stream booleanCombinations() { + return Stream.of( + Arguments.of(true, true, true), + Arguments.of(true, true, false), + Arguments.of(true, false, true), + Arguments.of(true, false, false), + Arguments.of(false, true, true), + Arguments.of(false, true, false), + Arguments.of(false, false, true), + Arguments.of(false, false, false) + ); + } + + // used to emulate sequence numbers returned by save latest API + private static List listOfNNumbers(int N) { + return LongStream.range(0, N).boxed().toList(); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index a1fb723681..2caa8f1e90 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -89,6 +89,7 @@ import static org.awaitility.Awaitility.await; import static org.eclipse.leshan.client.object.Security.noSec; import static org.hamcrest.core.IsInstanceOf.instanceOf; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -147,8 +148,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte " \"telemetry\": [],\n" + " \"attributeLwm2m\": {}\n" + " }"; - public static String OBSERVE_ATTRIBUTES_WITH_PARAMS = - + public static String TELEMETRY_WITHOUT_OBSERVE = " {\n" + " \"keyName\": {\n" + " \"/3_1.2/0/9\": \"batteryLevel\"\n" + @@ -161,6 +161,39 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte " ],\n" + " \"attributeLwm2m\": {}\n" + " }"; + public static String TELEMETRY_WITH_ONE_OBSERVE = + " {\n" + + " \"keyName\": {\n" + + " \"/3_1.2/0/9\": \"batteryLevel\"\n" + + " },\n" + + " \"observe\": [\n" + + " \"/3_1.2/0/9\"\n" + + " ],\n" + + " \"attribute\": [\n" + + " ],\n" + + " \"telemetry\": [\n" + + " \"/3_1.2/0/9\"\n" + + " ],\n" + + " \"attributeLwm2m\": {}\n" + + " }"; + + public static String TELEMETRY_WITH_MANY_OBSERVE = + " {\n" + + " \"keyName\": {\n" + + " \"/3_1.2/0/9\": \"batteryLevel\",\n" + + " \"/3_1.2/0/20\": \"batteryStatus\"\n" + + " },\n" + + " \"observe\": [\n" + + " \"/3_1.2/0/9\",\n" + + " \"/3_1.2/0/20\"\n" + + " ],\n" + + " \"attribute\": [],\n" + + " \"telemetry\": [\n" + + " \"/3_1.2/0/9\",\n" + + " \"/3_1.2/0/20\"\n" + + " ],\n" + + " \"attributeLwm2m\": {}\n" + + " }"; public static final String CLIENT_LWM2M_SETTINGS = " {\n" + @@ -217,7 +250,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte LwM2MDeviceCredentials deviceCredentials, String endpoint, boolean queueMode) throws Exception { - Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS, getBootstrapServerCredentialsNoSec(NONE)); + Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(TELEMETRY_WITHOUT_OBSERVE, getBootstrapServerCredentialsNoSec(NONE)); DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + endpoint, transportConfiguration); Device device = createLwm2mDevice(deviceCredentials, endpoint, deviceProfile.getId()); @@ -314,7 +347,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte public void createNewClient(Security security, Security securityBs, boolean isRpc, String endpoint, Integer clientDtlsCidLength, boolean queueMode, String deviceIdStr) throws Exception { this.clientDestroy(false); - lwM2MTestClient = new LwM2MTestClient(this.executor, endpoint); + lwM2MTestClient = new LwM2MTestClient(this.executor, endpoint, resources); try (ServerSocket socket = new ServerSocket(0)) { int clientPort = socket.getLocalPort(); @@ -327,7 +360,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte private void clientDestroy(boolean isAfter) { try { - if (lwM2MTestClient != null) { + if (lwM2MTestClient != null && lwM2MTestClient.getLeshanClient() != null) { if (isAfter) { sendObserveCancelAllWithAwait(lwM2MTestClient.getDeviceIdStr()); awaitDeleteDevice(lwM2MTestClient.getDeviceIdStr()); @@ -425,9 +458,15 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); return rpcActualResult.get("value").asText(); } + protected void sendRpcObserveWithContainsLwM2mSingleResource(String params) throws Exception { + String rpcActualResult = sendRpcObserveOkWithResultValue("Observe", params); + assertTrue(rpcActualResult.contains("LwM2mSingleResource") || rpcActualResult.contains("LwM2mMultipleResource")); + } + protected String sendRpcObserveOk(String method, String params) throws Exception { return sendObserveOK(method, params, lwM2MTestClient.getDeviceIdStr()); } + protected String sendObserveOK(String method, String params, String deviceIdStr) throws Exception { String sendRpcRequest; if (params == null) { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java index 4aa47eddf5..a40c937de8 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java @@ -17,8 +17,9 @@ package org.thingsboard.server.transport.lwm2m; public class Lwm2mTestHelper { + public static final String[] lwm2mClientResources = new String[]{"3.xml", "5.xml", "6.xml", "9.xml", "19.xml", "3303.xml"}; + // Models - public static final String[] resources = new String[]{"0.xml", "1.xml", "2.xml", "3.xml", "5.xml", "6.xml", "9.xml", "19.xml", "3303.xml"}; public static final int BINARY_APP_DATA_CONTAINER = 19; public static final int TEMPERATURE_SENSOR = 3303; diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java index 387085c511..ed846bc375 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java @@ -116,7 +116,7 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INST import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_1; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_12; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.TEMPERATURE_SENSOR; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resources; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.lwm2mClientResources; import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.setDtlsConnectorConfigCidLength; @@ -126,6 +126,7 @@ public class LwM2MTestClient { private final ScheduledExecutorService executor; private final String endpoint; + private final String[] modelResources; private LeshanClient leshanClient; private SimpleLwM2MDevice lwM2MDevice; private FwLwM2MDevice fwLwM2MDevice; @@ -147,10 +148,24 @@ public class LwM2MTestClient { Assert.assertNull("client already initialized", leshanClient); this.defaultLwM2mUplinkMsgHandlerTest = defaultLwM2mUplinkMsgHandler; this.clientContext = clientContext; - List models = new ArrayList<>(); - for (String resourceName : resources) { + + List models = ObjectLoader.loadAllDefault(); + for (String resourceName : lwm2mClientResources) { models.addAll(ObjectLoader.loadDdfFile(LwM2MTestClient.class.getClassLoader().getResourceAsStream("lwm2m/" + resourceName), resourceName)); } + if (this.modelResources != null) { + List modelsRes = new ArrayList<>(); + for (String resourceName : this.modelResources) { + modelsRes.addAll(ObjectLoader.loadDdfFile(LwM2MTestClient.class.getClassLoader().getResourceAsStream("lwm2m/" + resourceName), resourceName)); + } + Set idsToRemove = new HashSet<>(); + for (ObjectModel model : modelsRes) { + idsToRemove.add(model.id); + } + models.removeIf(model -> idsToRemove.contains(model.id)); + models.addAll(modelsRes); + } + LwM2mModel model = new StaticModel(models); ObjectsInitializer initializer = new ObjectsInitializer(model); if (securityBs != null && security != null) { @@ -458,6 +473,12 @@ public class LwM2MTestClient { } } + public void stop(boolean deregister) { + if (leshanClient != null) { + leshanClient.stop(deregister); + } + } + private void awaitClientAfterStartConnectLw() { LwM2mClient lwM2MClient = this.clientContext.getClientByEndpoint(endpoint); Mockito.doAnswer(invocationOnMock -> null).when(defaultLwM2mUplinkMsgHandlerTest).initAttributes(lwM2MClient, true); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java index a6a850a897..e94a6b4822 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java @@ -93,6 +93,7 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl try { executorService.scheduleWithFixedDelay(() -> { fireResourceChange(9); + fireResourceChange(20); } , 1, 1, TimeUnit.SECONDS); // 2 sec // , 1800000, 1800000, TimeUnit.MILLISECONDS); // 30 MIN diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/Ota5LwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/Ota5LwM2MIntegrationTest.java index a50f7df6a2..4ed06b99f2 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/Ota5LwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/Ota5LwM2MIntegrationTest.java @@ -51,7 +51,7 @@ public class Ota5LwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { @Test public void testFirmwareUpdateWithClientWithoutFirmwareOtaInfoFromProfile_IsNotSupported() throws Exception { - Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS, getBootstrapServerCredentialsNoSec(NONE)); + Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(TELEMETRY_WITHOUT_OBSERVE, getBootstrapServerCredentialsNoSec(NONE)); DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + this.CLIENT_ENDPOINT_WITHOUT_FW_INFO, transportConfiguration); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_WITHOUT_FW_INFO)); final Device device = createLwm2mDevice(deviceCredentials, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO, deviceProfile.getId()); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationObserveTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationObserveTest.java deleted file mode 100644 index 7195b0d229..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationObserveTest.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.transport.lwm2m.rpc; - -import org.thingsboard.server.dao.service.DaoSqlTest; -import static org.junit.Assert.assertTrue; - -@DaoSqlTest -public abstract class AbstractRpcLwM2MIntegrationObserveTest extends AbstractRpcLwM2MIntegrationTest{ - private final String[] RESOURCES_RPC_MULTIPLE_19 = new String[]{"0.xml", "1.xml", "2.xml", "3.xml", "5.xml", "6.xml", "9.xml", "19.xml", "3303.xml"}; - - public AbstractRpcLwM2MIntegrationObserveTest() { - setResources(this.RESOURCES_RPC_MULTIPLE_19); - } - - protected void sendRpcObserveWithContainsLwM2mSingleResource(String params) throws Exception { - String rpcActualResult = sendRpcObserveOkWithResultValue("Observe", params); - assertTrue(rpcActualResult.contains("LwM2mSingleResource") || rpcActualResult.contains("LwM2mMultipleResource")); - } -} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationObserve_Ver_1_0_Test.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationObserve_Ver_1_0_Test.java new file mode 100644 index 0000000000..0b7faafd7b --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationObserve_Ver_1_0_Test.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.rpc; + +import org.thingsboard.server.dao.service.DaoSqlTest; + +@DaoSqlTest +public abstract class AbstractRpcLwM2MIntegrationObserve_Ver_1_0_Test extends AbstractRpcLwM2MIntegrationTest{ + + public AbstractRpcLwM2MIntegrationObserve_Ver_1_0_Test() { + String[] RESOURCES_RPC_VER_1_1 = new String[]{"3-1_0.xml", "5.xml", "6.xml", "9.xml", "19.xml"}; + setResources(RESOURCES_RPC_VER_1_1); + } +} + diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationObserve_Ver_1_1_Test.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationObserve_Ver_1_1_Test.java new file mode 100644 index 0000000000..0a9290b0c4 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationObserve_Ver_1_1_Test.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.rpc; + +import org.thingsboard.server.dao.service.DaoSqlTest; + +@DaoSqlTest +public abstract class AbstractRpcLwM2MIntegrationObserve_Ver_1_1_Test extends AbstractRpcLwM2MIntegrationTest{ + + public AbstractRpcLwM2MIntegrationObserve_Ver_1_1_Test() { + String[] RESOURCES_RPC_VER_1_1 = new String[]{"3-1_1.xml", "5.xml", "6.xml", "9.xml", "19.xml"}; + setResources(RESOURCES_RPC_VER_1_1); + } +} + diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/alarm/AlarmMsgConstructorFactory.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationObserve_Ver_1_2_Test.java similarity index 58% rename from application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/alarm/AlarmMsgConstructorFactory.java rename to application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationObserve_Ver_1_2_Test.java index ec2e36d431..e52ca15634 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/alarm/AlarmMsgConstructorFactory.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationObserve_Ver_1_2_Test.java @@ -13,14 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.edge.rpc.constructor.alarm; +package org.thingsboard.server.transport.lwm2m.rpc; -import org.springframework.stereotype.Component; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.constructor.BaseMsgConstructorFactory; +import org.thingsboard.server.dao.service.DaoSqlTest; -@Component -@TbCoreComponent -public class AlarmMsgConstructorFactory extends BaseMsgConstructorFactory { +@DaoSqlTest +public abstract class AbstractRpcLwM2MIntegrationObserve_Ver_1_2_Test extends AbstractRpcLwM2MIntegrationTest{ + public AbstractRpcLwM2MIntegrationObserve_Ver_1_2_Test() { + String[] RESOURCES_RPC_VER_1_1 = new String[]{"3.xml", "5.xml", "6.xml", "9.xml", "19.xml"}; + setResources(RESOURCES_RPC_VER_1_1); + } } + diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java index d5c9683c39..de4845a220 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java @@ -63,7 +63,7 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_14; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_9; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.TEMPERATURE_SENSOR; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resources; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.lwm2mClientResources; import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId; @Slf4j @@ -102,7 +102,7 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg protected LwM2mTransportServerHelper lwM2mTransportServerHelperTest; public AbstractRpcLwM2MIntegrationTest() { - setResources(resources); + setResources(lwm2mClientResources); } @Before @@ -110,7 +110,7 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg if (this.getClass().getSimpleName().equals("RpcLwm2mIntegrationWriteCborTest")) { supportFormatOnly_SenMLJSON_SenMLCBOR = true; } - if (this.getClass().getSimpleName().equals("RpcLwm2mIntegrationObserveTest")) { + if (this.getClass().getSimpleName().contains("RpcLwm2mIntegrationObserve")) { initRpc(0); } else if (this.getClass().getSimpleName().equals("RpcLwm2mIntegrationReadCollectedValueTest")) { initRpc(3303); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2MIntegrationObserveCompositeTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2MIntegrationObserveCompositeTest.java index 56de08e66d..aa05b8073c 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2MIntegrationObserveCompositeTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2MIntegrationObserveCompositeTest.java @@ -21,7 +21,7 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.ResponseCode; import org.junit.Test; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationObserveTest; +import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; import java.util.Objects; import java.util.concurrent.TimeUnit; @@ -50,7 +50,7 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_IN import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId; @Slf4j -public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MIntegrationObserveTest { +public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MIntegrationTest { /** * ObserveComposite {"ids":["5/0/7", "5/0/5", "5/0/3", "3/0/9", "19/1/0/0"]} - Ok diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java index 57213e08cd..425c7a3d6b 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java @@ -24,7 +24,7 @@ import org.eclipse.leshan.server.registration.Registration; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; -import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationObserveTest; +import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL; import static org.junit.Assert.assertEquals; @@ -41,7 +41,7 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId; @Slf4j -public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationObserveTest { +public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationTest { @Before public void setupObserveTest() throws Exception { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserve_Ver_1_0_Test.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserve_Ver_1_0_Test.java new file mode 100644 index 0000000000..9c2fbd3313 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserve_Ver_1_0_Test.java @@ -0,0 +1,48 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.rpc.sql; + +import lombok.extern.slf4j.Slf4j; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationObserve_Ver_1_0_Test; +import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationObserve_Ver_1_1_Test; + +import static org.junit.Assert.assertTrue; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_9; + +@Slf4j +public class RpcLwm2mIntegrationObserve_Ver_1_0_Test extends AbstractRpcLwM2MIntegrationObserve_Ver_1_0_Test { + + @Before + public void setupObserveTest() throws Exception { + awaitObserveReadAll(4,lwM2MTestClient.getDeviceIdStr()); + } + + /** + * Observe "3_1.0/0/9" + * @throws Exception + */ + @Test + public void testObserveOneResource_Result_CONTENT_Value_Count_3_After_Cancel_Count_2() throws Exception { + long initSendTelemetryAtCount = countSendParametersOnThingsboardTelemetryResource(RESOURCE_ID_NAME_3_9); + sendObserveCancelAllWithAwait(lwM2MTestClient.getDeviceIdStr()); + sendRpcObserveWithContainsLwM2mSingleResource(idVer_3_0_9); + updateRegAtLeastOnceAfterAction(); + long lastSendTelemetryAtCount = countSendParametersOnThingsboardTelemetryResource(RESOURCE_ID_NAME_3_9); + assertTrue(lastSendTelemetryAtCount > initSendTelemetryAtCount); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserve_Ver_1_1_Test.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserve_Ver_1_1_Test.java new file mode 100644 index 0000000000..79d09721e9 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserve_Ver_1_1_Test.java @@ -0,0 +1,46 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.rpc.sql; + +import lombok.extern.slf4j.Slf4j; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationObserve_Ver_1_1_Test; +import static org.junit.Assert.assertTrue; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_9; + +@Slf4j +public class RpcLwm2mIntegrationObserve_Ver_1_1_Test extends AbstractRpcLwM2MIntegrationObserve_Ver_1_1_Test { + + @Before + public void setupObserveTest() throws Exception { + awaitObserveReadAll(4,lwM2MTestClient.getDeviceIdStr()); + } + + /** + * Observe "3_1.1/0/9" + * @throws Exception + */ + @Test + public void testObserveOneResource_Result_CONTENT_Value_Count_3_After_Cancel_Count_2() throws Exception { + long initSendTelemetryAtCount = countSendParametersOnThingsboardTelemetryResource(RESOURCE_ID_NAME_3_9); + sendObserveCancelAllWithAwait(lwM2MTestClient.getDeviceIdStr()); + sendRpcObserveWithContainsLwM2mSingleResource(idVer_3_0_9); + updateRegAtLeastOnceAfterAction(); + long lastSendTelemetryAtCount = countSendParametersOnThingsboardTelemetryResource(RESOURCE_ID_NAME_3_9); + assertTrue(lastSendTelemetryAtCount > initSendTelemetryAtCount); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserve_Ver_1_2_Test.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserve_Ver_1_2_Test.java new file mode 100644 index 0000000000..ec45c2beac --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserve_Ver_1_2_Test.java @@ -0,0 +1,48 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.rpc.sql; + +import lombok.extern.slf4j.Slf4j; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationObserve_Ver_1_0_Test; +import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationObserve_Ver_1_2_Test; + +import static org.junit.Assert.assertTrue; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_9; + +@Slf4j +public class RpcLwm2mIntegrationObserve_Ver_1_2_Test extends AbstractRpcLwM2MIntegrationObserve_Ver_1_2_Test { + + @Before + public void setupObserveTest() throws Exception { + awaitObserveReadAll(4,lwM2MTestClient.getDeviceIdStr()); + } + + /** + * Observe "3_1.2/0/9" + * @throws Exception + */ + @Test + public void testObserveOneResource_Result_CONTENT_Value_Count_3_After_Cancel_Count_2() throws Exception { + long initSendTelemetryAtCount = countSendParametersOnThingsboardTelemetryResource(RESOURCE_ID_NAME_3_9); + sendObserveCancelAllWithAwait(lwM2MTestClient.getDeviceIdStr()); + sendRpcObserveWithContainsLwM2mSingleResource(idVer_3_0_9); + updateRegAtLeastOnceAfterAction(); + long lastSendTelemetryAtCount = countSendParametersOnThingsboardTelemetryResource(RESOURCE_ID_NAME_3_9); + assertTrue(lastSendTelemetryAtCount > initSendTelemetryAtCount); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java index eaade683d8..3f43c59068 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java @@ -196,7 +196,7 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M false); } - protected void basicTestConnection(Security security, Security securityBs, + protected Device basicTestConnection(Security security, Security securityBs, LwM2MDeviceCredentials deviceCredentials, String endpoint, Lwm2mDeviceProfileTransportConfiguration transportConfiguration, @@ -227,6 +227,7 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M return lwM2MTestClient.getClientStates().contains(finishState) || lwM2MTestClient.getClientStates().contains(ON_UPDATE_SUCCESS); }); Assert.assertTrue(lwM2MTestClient.getClientStates().containsAll(expectedStatuses)); + return device; } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java index ac1c0866de..42014a4a75 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java @@ -16,10 +16,13 @@ package org.thingsboard.server.transport.lwm2m.security.sql; import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.core.util.Hex; +import org.junit.Assert; import org.junit.Test; import org.springframework.test.web.servlet.MvcResult; +import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredential; @@ -27,7 +30,6 @@ import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTrans import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; import java.nio.charset.StandardCharsets; - import static org.eclipse.leshan.client.object.Security.psk; import static org.eclipse.leshan.client.object.Security.pskBootstrap; import static org.junit.Assert.assertEquals; @@ -37,6 +39,7 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClient import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.BOTH; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE; +@Slf4j public class PskLwm2mIntegrationTest extends AbstractSecurityLwM2MIntegrationTest { //Lwm2m only @@ -66,6 +69,82 @@ public class PskLwm2mIntegrationTest extends AbstractSecurityLwM2MIntegrationTes ON_REGISTRATION_SUCCESS, true); } + @Test + public void testWithPskConnectLwm2mOneObserveSuccessUpdateProfileManyObserveUpdateRegistrationSuccess() throws Exception { + String clientEndpoint = CLIENT_ENDPOINT_PSK; + String identity = CLIENT_PSK_IDENTITY; + String keyPsk = CLIENT_PSK_KEY; + PSKClientCredential clientCredentials = new PSKClientCredential(); + clientCredentials.setEndpoint(clientEndpoint); + clientCredentials.setIdentity(identity); + clientCredentials.setKey(keyPsk); + Security security = psk(SECURE_URI, + shortServerId, + identity.getBytes(StandardCharsets.UTF_8), + Hex.decodeHex(keyPsk.toCharArray())); + Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(TELEMETRY_WITH_ONE_OBSERVE, getBootstrapServerCredentialsSecure(PSK, NONE)); + LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, null, null, PSK, false); + String awaitAlias = "await on client state (Psk_Lwm2m)"; + Device lwm2mDevice = this.basicTestConnection(security, + null, + deviceCredentials, + clientEndpoint, + transportConfiguration, + awaitAlias, + expectedStatusesRegistrationLwm2mSuccess, + false, + ON_REGISTRATION_SUCCESS, + true); + + awaitObserveReadAll(1, lwm2mDevice.getId().getId().toString()); + DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/" + lwm2mDevice.getDeviceProfileId().getId().toString(), DeviceProfile.class); + transportConfiguration = getTransportConfiguration(TELEMETRY_WITH_MANY_OBSERVE, getBootstrapServerCredentialsSecure(PSK, NONE)); + foundDeviceProfile.getProfileData().setTransportConfiguration(transportConfiguration); + DeviceProfile lwm2mDeviceProfileManyParams = doPost("/api/deviceProfile", foundDeviceProfile, DeviceProfile.class); + Assert.assertNotNull(lwm2mDeviceProfileManyParams); + awaitObserveReadAll(2, lwm2mDevice.getId().getId().toString()); + awaitUpdateReg(3); + } + @Test + public void testWithPskConnectLwm2mSuccessObserveSuccessUnRegClientUpdateProfileObserveConnectLwm2mSuccessOWithNewObserve() throws Exception { + String clientEndpoint = CLIENT_ENDPOINT_PSK; + String identity = CLIENT_PSK_IDENTITY; + String keyPsk = CLIENT_PSK_KEY; + PSKClientCredential clientCredentials = new PSKClientCredential(); + clientCredentials.setEndpoint(clientEndpoint); + clientCredentials.setIdentity(identity); + clientCredentials.setKey(keyPsk); + Security security = psk(SECURE_URI, + shortServerId, + identity.getBytes(StandardCharsets.UTF_8), + Hex.decodeHex(keyPsk.toCharArray())); + Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(TELEMETRY_WITH_ONE_OBSERVE, getBootstrapServerCredentialsSecure(PSK, NONE)); + LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, null, null, PSK, false); + String awaitAlias = "await on client state (Psk_Lwm2m)"; + Device lwm2mDevice = this.basicTestConnection(security, + null, + deviceCredentials, + clientEndpoint, + transportConfiguration, + awaitAlias, + expectedStatusesRegistrationLwm2mSuccess, + false, + ON_REGISTRATION_SUCCESS, + true); + + awaitObserveReadAll(1, lwm2mDevice.getId().getId().toString()); + lwM2MTestClient.stop(true); + + DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/" + lwm2mDevice.getDeviceProfileId().getId().toString(), DeviceProfile.class); + transportConfiguration = getTransportConfiguration(TELEMETRY_WITH_MANY_OBSERVE, getBootstrapServerCredentialsSecure(PSK, NONE)); + foundDeviceProfile.getProfileData().setTransportConfiguration(transportConfiguration); + DeviceProfile lwm2mDeviceProfileManyParams = doPost("/api/deviceProfile", foundDeviceProfile, DeviceProfile.class); + Assert.assertNotNull(lwm2mDeviceProfileManyParams); + + lwM2MTestClient.start(true); + awaitObserveReadAll(2, lwm2mDevice.getId().getId().toString()); + awaitUpdateReg(3); + } @Test public void testWithPskConnectLwm2mBadPskKeyByLength_BAD_REQUEST() throws Exception { diff --git a/application/src/test/resources/lwm2m/3-1_0.xml b/application/src/test/resources/lwm2m/3-1_0.xml new file mode 100644 index 0000000000..4f5261d263 --- /dev/null +++ b/application/src/test/resources/lwm2m/3-1_0.xml @@ -0,0 +1,327 @@ + + + + + + + Device + + 3 + urn:oma:lwm2m:oma:3 + Single + Mandatory + + + Manufacturer + R + Single + Optional + String + + + + + + Model Number + R + Single + Optional + String + + + + + + Serial Number + R + Single + Optional + String + + + + + + Firmware Version + R + Single + Optional + String + + + + + + Reboot + E + Single + Mandatory + + + + + + + Factory Reset + E + Single + Optional + + + + + + + Available Power Sources + R + Multiple + Optional + Integer + 0-7 + + + + + Power Source Voltage + R + Multiple + Optional + Integer + + mV + + + + Power Source Current + R + Multiple + Optional + Integer + + mA + + + + Battery Level + R + Single + Optional + Integer + 0-100 + % + + + + Memory Free + R + Single + Optional + Integer + + KB + + + + Error Code + R + Multiple + Mandatory + Integer + 0-8 + + + + + Reset Error Code + E + Single + Optional + + + + + + + Current Time + RW + Single + Optional + Time + + + + + + UTC Offset + RW + Single + Optional + String + + + + + + Timezone + RW + Single + Optional + String + + + + + + Supported Binding and Modes + R + Single + Mandatory + String + + + + + Device Type + R + Single + Optional + String + + + + + Hardware Version + R + Single + Optional + String + + + + + Software Version + R + Single + Optional + String + + + + + Battery Status + R + Single + Optional + Integer + 0-6 + + + + Memory Total + R + Single + Optional + Integer + + + + + ExtDevInfo + R + Multiple + Optional + Objlnk + + + + + + + diff --git a/application/src/test/resources/lwm2m/3-1_1.xml b/application/src/test/resources/lwm2m/3-1_1.xml new file mode 100644 index 0000000000..01151a57b7 --- /dev/null +++ b/application/src/test/resources/lwm2m/3-1_1.xml @@ -0,0 +1,331 @@ + + + + + + + Device + + 3 + urn:oma:lwm2m:oma:3:1.1 + 1.1 + 1.1 + Single + Mandatory + + + Manufacturer + R + Single + Optional + String + + + + + + Model Number + R + Single + Optional + String + + + + + + Serial Number + R + Single + Optional + String + + + + + + Firmware Version + R + Single + Optional + String + + + + + + Reboot + E + Single + Mandatory + + + + + + + Factory Reset + E + Single + Optional + + + + + + + Available Power Sources + R + Multiple + Optional + Integer + 0..7 + + + + + Power Source Voltage + R + Multiple + Optional + Integer + + + + + + Power Source Current + R + Multiple + Optional + Integer + + + + + + Battery Level + R + Single + Optional + Integer + 0..100 + % + + + + Memory Free + R + Single + Optional + Integer + + + + + + Error Code + R + Multiple + Mandatory + Integer + 0..8 + + + + + Reset Error Code + E + Single + Optional + + + + + + + Current Time + RW + Single + Optional + Time + + + + + + UTC Offset + RW + Single + Optional + String + + + + + + Timezone + RW + Single + Optional + String + + + + + + Supported Binding and Modes + R + Single + Mandatory + String + + + + + Device Type + R + Single + Optional + String + + + + + Hardware Version + R + Single + Optional + String + + + + + Software Version + R + Single + Optional + String + + + + + Battery Status + R + Single + Optional + Integer + 0..6 + + + + Memory Total + R + Single + Optional + Integer + + + + + ExtDevInfo + R + Multiple + Optional + Objlnk + + + + + + + diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/RedisTbTransactionalCache.java b/common/cache/src/main/java/org/thingsboard/server/cache/RedisTbTransactionalCache.java index 8ee58ab8eb..379f08af37 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/RedisTbTransactionalCache.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/RedisTbTransactionalCache.java @@ -19,9 +19,11 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.support.NullValue; +import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisStringCommands; +import org.springframework.data.redis.connection.ReturnType; import org.springframework.data.redis.connection.jedis.JedisClusterConnection; import org.springframework.data.redis.connection.jedis.JedisConnection; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; @@ -269,4 +271,24 @@ public abstract class RedisTbTransactionalCache extends RedisTbTransactionalCache implements VersionedTbCache { @@ -65,19 +62,6 @@ public abstract class VersionedRedisTbCache bundles; public WidgetTypeInfo() { super(); @@ -48,14 +59,23 @@ public class WidgetTypeInfo extends BaseWidgetType { } public WidgetTypeInfo(WidgetTypeInfo widgetTypeInfo) { + this(widgetTypeInfo, Collections.emptyList()); + } + + public WidgetTypeInfo(WidgetTypeInfo widgetTypeInfo, List bundles) { super(widgetTypeInfo); this.image = widgetTypeInfo.getImage(); this.description = widgetTypeInfo.getDescription(); this.tags = widgetTypeInfo.getTags(); this.widgetType = widgetTypeInfo.getWidgetType(); + this.bundles = bundles; } public WidgetTypeInfo(WidgetTypeDetails widgetTypeDetails) { + this(widgetTypeDetails, Collections.emptyList()); + } + + public WidgetTypeInfo(WidgetTypeDetails widgetTypeDetails, List bundles) { super(widgetTypeDetails); this.image = widgetTypeDetails.getImage(); this.description = widgetTypeDetails.getDescription(); @@ -65,5 +85,7 @@ public class WidgetTypeInfo extends BaseWidgetType { } else { this.widgetType = ""; } + this.bundles = bundles; } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java index 005e4ca252..82ba041040 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java @@ -17,9 +17,9 @@ package org.thingsboard.server.common.data.widget; import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.Setter; +import lombok.ToString; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.ExportableEntity; import org.thingsboard.server.common.data.HasImage; @@ -32,58 +32,45 @@ import org.thingsboard.server.common.data.id.WidgetsBundleId; import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; +import java.io.Serial; + @Schema +@Data @EqualsAndHashCode(callSuper = true) public class WidgetsBundle extends BaseData implements HasName, HasTenantId, ExportableEntity, HasTitle, HasImage, HasVersion { + @Serial private static final long serialVersionUID = -7627368878362410489L; - @Getter - @Setter @Schema(description = "JSON object with Tenant Id.", accessMode = Schema.AccessMode.READ_ONLY) private TenantId tenantId; @NoXss @Length(fieldName = "alias") - @Getter - @Setter @Schema(description = "Unique alias that is used in widget types as a reference widget bundle", accessMode = Schema.AccessMode.READ_ONLY) private String alias; @NoXss @Length(fieldName = "title") - @Getter - @Setter @Schema(description = "Title used in search and UI", accessMode = Schema.AccessMode.READ_ONLY) private String title; - @Getter - @Setter @Schema(description = "Relative or external image URL. Replaced with image data URL (Base64) in case of relative URL and 'inlineImages' option enabled.", accessMode = Schema.AccessMode.READ_ONLY) + @ToString.Exclude private String image; - @Getter - @Setter @Schema(description = "Whether widgets bundle contains SCADA symbol widget types.", accessMode = Schema.AccessMode.READ_ONLY) private boolean scada; @NoXss @Length(fieldName = "description", max = 1024) - @Getter - @Setter @Schema(description = "Description", accessMode = Schema.AccessMode.READ_ONLY) private String description; - @Getter - @Setter @Schema(description = "Order", accessMode = Schema.AccessMode.READ_ONLY) private Integer order; - @Getter - @Setter private WidgetsBundleId externalId; - @Getter - @Setter private Long version; public WidgetsBundle() { @@ -128,16 +115,4 @@ public class WidgetsBundle extends BaseData implements HasName, public String getName() { return title; } - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder("WidgetsBundle{"); - sb.append("tenantId=").append(tenantId); - sb.append(", alias='").append(alias).append('\''); - sb.append(", title='").append(title).append('\''); - sb.append(", description='").append(description).append('\''); - sb.append('}'); - return sb.toString(); - } - } diff --git a/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java b/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java index fcef33b968..3936b3ee63 100644 --- a/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java +++ b/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java @@ -136,7 +136,7 @@ public class EdgeGrpcClient implements EdgeRpcClient { .setConnectRequestMsg(ConnectRequestMsg.newBuilder() .setEdgeRoutingKey(edgeKey) .setEdgeSecret(edgeSecret) - .setEdgeVersion(EdgeVersion.V_3_9_0) + .setEdgeVersion(EdgeVersion.V_4_0_0) .setMaxInboundMessageSize(maxInboundMessageSize) .build()) .build()); diff --git a/common/edge-api/src/main/proto/edge.proto b/common/edge-api/src/main/proto/edge.proto index 0557c5f5d4..252530b845 100644 --- a/common/edge-api/src/main/proto/edge.proto +++ b/common/edge-api/src/main/proto/edge.proto @@ -41,6 +41,7 @@ enum EdgeVersion { V_3_7_0 = 7; V_3_8_0 = 8; V_3_9_0 = 9; + V_4_0_0 = 10; } /** @@ -92,12 +93,10 @@ message ConnectResponseMsg { } message SyncRequestMsg { - bool syncRequired = 1 [deprecated = true]; optional bool fullSync = 2; } -message SyncCompletedMsg { -} +message SyncCompletedMsg {} message EdgeConfiguration { int64 edgeIdMSB = 1; @@ -120,7 +119,7 @@ enum UpdateMsgType { ENTITY_DELETED_RPC_MESSAGE = 2; ALARM_ACK_RPC_MESSAGE = 3; ALARM_CLEAR_RPC_MESSAGE = 4; - ENTITY_MERGE_RPC_MESSAGE = 5 [deprecated = true]; + // use 6 as a next number } message EntityDataProto { @@ -144,68 +143,18 @@ message RuleChainUpdateMsg { UpdateMsgType msgType = 1; int64 idMSB = 2; int64 idLSB = 3; - string name = 4 [deprecated = true]; - optional int64 firstRuleNodeIdMSB = 5 [deprecated = true]; - optional int64 firstRuleNodeIdLSB = 6 [deprecated = true]; - bool root = 7 [deprecated = true]; - bool debugMode = 8 [deprecated = true]; - string configuration = 9 [deprecated = true]; string entity = 10; } message RuleChainMetadataUpdateMsg { UpdateMsgType msgType = 1; - int64 ruleChainIdMSB = 2 [deprecated = true]; - int64 ruleChainIdLSB = 3 [deprecated = true]; - int32 firstNodeIndex = 4 [deprecated = true]; - repeated RuleNodeProto nodes = 5 [deprecated = true]; - repeated NodeConnectionInfoProto connections = 6 [deprecated = true]; - repeated RuleChainConnectionInfoProto ruleChainConnections = 7 [deprecated = true]; string entity = 8; } -message RuleNodeProto { - option deprecated = true; - int64 idMSB = 1; - int64 idLSB = 2; - string type = 3; - string name = 4; - bool debugMode = 5 [deprecated = true]; - string configuration = 6; - string additionalInfo = 7; - bool singletonMode = 8; - int32 configurationVersion = 9; - string debugSettings = 10; -} - -message NodeConnectionInfoProto { - option deprecated = true; - int32 fromIndex = 1; - int32 toIndex = 2; - string type = 3; -} - -message RuleChainConnectionInfoProto { - option deprecated = true; - int32 fromIndex = 1; - int64 targetRuleChainIdMSB = 2; - int64 targetRuleChainIdLSB = 3; - string type = 4; - string additionalInfo = 5; -} - message DashboardUpdateMsg { UpdateMsgType msgType = 1; int64 idMSB = 2; int64 idLSB = 3; - optional int64 customerIdMSB = 4 [deprecated = true]; - optional int64 customerIdLSB = 5 [deprecated = true]; - string title = 6 [deprecated = true]; - string configuration = 7 [deprecated = true]; - optional string assignedCustomers = 8 [deprecated = true]; - optional string image = 9 [deprecated = true]; - optional int32 mobileOrder = 10 [deprecated = true]; - bool mobileHide = 11 [deprecated = true]; string entity = 12; } @@ -213,20 +162,6 @@ message DeviceUpdateMsg { UpdateMsgType msgType = 1; int64 idMSB = 2; int64 idLSB = 3; - optional int64 customerIdMSB = 4 [deprecated = true]; - optional int64 customerIdLSB = 5 [deprecated = true]; - optional int64 deviceProfileIdMSB = 6 [deprecated = true]; - optional int64 deviceProfileIdLSB = 7 [deprecated = true]; - string name = 8 [deprecated = true]; - string type = 9 [deprecated = true]; - optional string label = 10 [deprecated = true]; - optional string additionalInfo = 11 [deprecated = true]; - optional string conflictName = 12 [deprecated = true]; - optional int64 firmwareIdMSB = 13 [deprecated = true]; - optional int64 firmwareIdLSB = 14 [deprecated = true]; - optional bytes deviceDataBytes = 15 [deprecated = true]; - optional int64 softwareIdMSB = 16 [deprecated = true]; - optional int64 softwareIdLSB = 17 [deprecated = true]; string entity = 18; } @@ -234,24 +169,6 @@ message DeviceProfileUpdateMsg { UpdateMsgType msgType = 1; int64 idMSB = 2; int64 idLSB = 3; - string name = 4 [deprecated = true]; - optional string description = 5 [deprecated = true]; - bool default = 6 [deprecated = true]; - string type = 7 [deprecated = true]; - optional string transportType = 8 [deprecated = true]; - optional string provisionType = 9 [deprecated = true]; - int64 defaultRuleChainIdMSB = 10 [deprecated = true]; - int64 defaultRuleChainIdLSB = 11 [deprecated = true]; - string defaultQueueName = 12 [deprecated = true]; - bytes profileDataBytes = 13 [deprecated = true]; - optional string provisionDeviceKey = 14 [deprecated = true]; - optional bytes image = 15 [deprecated = true]; - optional int64 firmwareIdMSB = 16 [deprecated = true]; - optional int64 firmwareIdLSB = 17 [deprecated = true]; - optional int64 softwareIdMSB = 18 [deprecated = true]; - optional int64 softwareIdLSB = 19 [deprecated = true]; - optional int64 defaultDashboardIdMSB = 20 [deprecated = true]; - optional int64 defaultDashboardIdLSB = 21 [deprecated = true]; string entity = 22; } @@ -259,24 +176,10 @@ message AssetProfileUpdateMsg { UpdateMsgType msgType = 1; int64 idMSB = 2; int64 idLSB = 3; - string name = 4 [deprecated = true]; - optional string description = 5 [deprecated = true]; - bool default = 6 [deprecated = true]; - int64 defaultRuleChainIdMSB = 7 [deprecated = true]; - int64 defaultRuleChainIdLSB = 8 [deprecated = true]; - int64 defaultDashboardIdMSB = 9 [deprecated = true]; - int64 defaultDashboardIdLSB = 10 [deprecated = true]; - optional string defaultQueueName = 11 [deprecated = true]; - optional bytes image = 12 [deprecated = true]; string entity = 13; } message DeviceCredentialsUpdateMsg { - int64 deviceIdMSB = 1 [deprecated = true]; - int64 deviceIdLSB = 2 [deprecated = true]; - string credentialsType = 3 [deprecated = true]; - string credentialsId = 4 [deprecated = true]; - optional string credentialsValue = 5 [deprecated = true]; string entity = 6; } @@ -284,14 +187,6 @@ message AssetUpdateMsg { UpdateMsgType msgType = 1; int64 idMSB = 2; int64 idLSB = 3; - optional int64 customerIdMSB = 4 [deprecated = true]; - optional int64 customerIdLSB = 5 [deprecated = true]; - string name = 6 [deprecated = true]; - string type = 7 [deprecated = true]; - optional string label = 8 [deprecated = true]; - optional string additionalInfo = 9 [deprecated = true]; - optional int64 assetProfileIdMSB = 10 [deprecated = true]; - optional int64 assetProfileIdLSB = 11 [deprecated = true]; string entity = 12; } @@ -299,14 +194,6 @@ message EntityViewUpdateMsg { UpdateMsgType msgType = 1; int64 idMSB = 2; int64 idLSB = 3; - optional int64 customerIdMSB = 4 [deprecated = true]; - optional int64 customerIdLSB = 5 [deprecated = true]; - string name = 6 [deprecated = true]; - string type = 7 [deprecated = true]; - int64 entityIdMSB = 8 [deprecated = true]; - int64 entityIdLSB = 9 [deprecated = true]; - EdgeEntityType entityType = 10 [deprecated = true]; - optional string additionalInfo = 11 [deprecated = true]; string entity = 12; } @@ -314,20 +201,6 @@ message AlarmUpdateMsg { UpdateMsgType msgType = 1; int64 idMSB = 2; int64 idLSB = 3; - string name = 4 [deprecated = true]; - string type = 5 [deprecated = true]; - string originatorType = 6 [deprecated = true]; - string originatorName = 7 [deprecated = true]; - string severity = 8 [deprecated = true]; - string status = 9 [deprecated = true]; - int64 startTs = 10 [deprecated = true]; - int64 endTs = 11 [deprecated = true]; - int64 ackTs = 12 [deprecated = true]; - int64 clearTs = 13 [deprecated = true]; - string details = 14 [deprecated = true]; - bool propagate = 15 [deprecated = true]; - bool propagateToOwner = 16 [deprecated = true]; - bool propagateToTenant = 17 [deprecated = true]; string entity = 18; } @@ -340,30 +213,11 @@ message CustomerUpdateMsg { UpdateMsgType msgType = 1; int64 idMSB = 2; int64 idLSB = 3; - string title = 4 [deprecated = true]; - optional string country = 5 [deprecated = true]; - optional string state = 6 [deprecated = true]; - optional string city = 7 [deprecated = true]; - optional string address = 8 [deprecated = true]; - optional string address2 = 9 [deprecated = true]; - optional string zip = 10 [deprecated = true]; - optional string phone = 11 [deprecated = true]; - optional string email = 12 [deprecated = true]; - optional string additionalInfo = 13 [deprecated = true]; string entity = 14; } message RelationUpdateMsg { UpdateMsgType msgType = 1; - int64 fromIdMSB = 2 [deprecated = true]; - int64 fromIdLSB = 3 [deprecated = true]; - string fromEntityType = 4 [deprecated = true]; - int64 toIdMSB = 5 [deprecated = true]; - int64 toIdLSB = 6 [deprecated = true]; - string toEntityType = 7 [deprecated = true]; - string type = 8 [deprecated = true]; - optional string typeGroup = 9 [deprecated = true]; - string additionalInfo = 10 [deprecated = true]; string entity = 11; } @@ -371,13 +225,6 @@ message UserUpdateMsg { UpdateMsgType msgType = 1; int64 idMSB = 2; int64 idLSB = 3; - optional int64 customerIdMSB = 4 [deprecated = true]; - optional int64 customerIdLSB = 5 [deprecated = true]; - string email = 6 [deprecated = true]; - string authority = 7 [deprecated = true]; - optional string firstName = 8 [deprecated = true]; - optional string lastName = 9 [deprecated = true]; - optional string additionalInfo = 10 [deprecated = true]; string entity = 11; } @@ -385,13 +232,7 @@ message WidgetsBundleUpdateMsg { UpdateMsgType msgType = 1; int64 idMSB = 2; int64 idLSB = 3; - string title = 4 [deprecated = true]; - string alias = 5 [deprecated = true]; - optional bytes image = 6 [deprecated = true]; - bool isSystem = 7 [deprecated = true]; - optional string description = 8 [deprecated = true]; optional string widgets = 9; - optional int32 order = 10 [deprecated = true]; string entity = 11; } @@ -399,63 +240,24 @@ message WidgetTypeUpdateMsg { UpdateMsgType msgType = 1; int64 idMSB = 2; int64 idLSB = 3; - optional string bundleAlias = 4 [deprecated = true]; - optional string alias = 5 [deprecated = true]; - optional string name = 6 [deprecated = true]; - optional string descriptorJson = 7 [deprecated = true]; - bool isSystem = 8 [deprecated = true]; - optional string image = 9 [deprecated = true]; - optional string description = 10 [deprecated = true]; - optional string fqn = 11 [deprecated = true]; - bool deprecated = 12 [deprecated = true]; - repeated string tags = 13 [deprecated = true]; string entity = 14; } message AdminSettingsUpdateMsg { - bool isSystem = 1 [deprecated = true]; - string key = 2 [deprecated = true]; - string jsonValue = 3 [deprecated = true]; string entity = 4; } message UserCredentialsUpdateMsg { - int64 userIdMSB = 1 [deprecated = true]; - int64 userIdLSB = 2 [deprecated = true]; - bool enabled = 3 [deprecated = true]; - string password = 4 [deprecated = true]; string entity = 5; } message TenantUpdateMsg { UpdateMsgType msgType = 1; - int64 idMSB = 2 [deprecated = true]; - int64 idLSB = 3 [deprecated = true]; - string title = 4 [deprecated = true]; - int64 profileIdMSB = 5 [deprecated = true]; - int64 profileIdLSB = 6 [deprecated = true]; - string region = 7 [deprecated = true]; - optional string country = 8 [deprecated = true]; - optional string state = 9 [deprecated = true]; - optional string city = 10 [deprecated = true]; - optional string address = 11 [deprecated = true]; - optional string address2 = 12 [deprecated = true]; - optional string zip = 13 [deprecated = true]; - optional string phone = 14 [deprecated = true]; - optional string email = 15 [deprecated = true]; - optional string additionalInfo = 16 [deprecated = true]; string entity = 17; } message TenantProfileUpdateMsg { UpdateMsgType msgType = 1; - int64 idMSB = 2 [deprecated = true]; - int64 idLSB = 3 [deprecated = true]; - string name = 4 [deprecated = true]; - optional string description = 5 [deprecated = true]; - bool default = 6 [deprecated = true]; - bool isolatedRuleChain = 7 [deprecated = true]; - bytes profileDataBytes = 8 [deprecated = true]; string entity = 9; } @@ -463,13 +265,6 @@ message ResourceUpdateMsg { UpdateMsgType msgType = 1; int64 idMSB = 2; int64 idLSB = 3; - string title = 4 [deprecated = true]; - string resourceType = 5 [deprecated = true]; - string resourceKey = 6 [deprecated = true]; - string fileName = 7 [deprecated = true]; - optional string data = 8 [deprecated = true]; - optional string etag = 9 [deprecated = true]; - bool isSystem = 10 [deprecated = true]; string entity = 11; } @@ -536,12 +331,6 @@ message DeviceCredentialsRequestMsg { int64 deviceIdLSB = 2; } -message DeviceProfileDevicesRequestMsg { - option deprecated = true; - int64 deviceProfileIdMSB = 1; - int64 deviceProfileIdLSB = 2; -} - message WidgetBundleTypesRequestMsg { int64 widgetBundleIdMSB = 1; int64 widgetBundleIdLSB = 2; @@ -580,29 +369,10 @@ message RpcResponseMsg { string error = 2; } -enum EdgeEntityType { - DEVICE = 0; - ASSET = 1; -} - message OtaPackageUpdateMsg { UpdateMsgType msgType = 1; int64 idMSB = 2; int64 idLSB = 3; - int64 deviceProfileIdMSB = 4 [deprecated = true]; - int64 deviceProfileIdLSB = 5 [deprecated = true]; - string type = 6 [deprecated = true]; - string title = 7 [deprecated = true]; - string version = 8 [deprecated = true]; - string tag = 9 [deprecated = true]; - optional string url = 10 [deprecated = true]; - optional string fileName = 11 [deprecated = true]; - optional string contentType = 12 [deprecated = true]; - optional string checksumAlgorithm = 13 [deprecated = true]; - optional string checksum = 14 [deprecated = true]; - optional int64 dataSize = 15 [deprecated = true]; - optional bytes data = 16 [deprecated = true]; - optional string additionalInfo = 17 [deprecated = true]; string entity = 18; } @@ -610,34 +380,9 @@ message QueueUpdateMsg { UpdateMsgType msgType = 1; int64 idMSB = 2; int64 idLSB = 3; - int64 tenantIdMSB = 4 [deprecated = true]; - int64 tenantIdLSB = 5 [deprecated = true]; - string name = 6 [deprecated = true]; - string topic = 7 [deprecated = true]; - int32 pollInterval = 8 [deprecated = true]; - int32 partitions = 9 [deprecated = true]; - bool consumerPerPartition = 10 [deprecated = true]; - int64 packProcessingTimeout = 11 [deprecated = true]; - SubmitStrategyProto submitStrategy = 12 [deprecated = true]; - ProcessingStrategyProto processingStrategy = 13 [deprecated = true]; string entity = 14; } -message SubmitStrategyProto { - option deprecated = true; - string type = 1; - int32 batchSize = 2; -} - -message ProcessingStrategyProto { - option deprecated = true; - string type = 1; - int32 retries = 2; - double failurePercentage = 3; - int64 pauseBetweenRetries = 4; - int64 maxPauseBetweenRetries = 5; -} - /** * Main Messages; */ @@ -655,7 +400,6 @@ message UplinkMsg { repeated UserCredentialsRequestMsg userCredentialsRequestMsg = 10; repeated DeviceCredentialsRequestMsg deviceCredentialsRequestMsg = 11; repeated DeviceRpcCallMsg deviceRpcCallMsg = 12; - repeated DeviceProfileDevicesRequestMsg deviceProfileDevicesRequestMsg = 13 [deprecated = true]; repeated WidgetBundleTypesRequestMsg widgetBundleTypesRequestMsg = 14; repeated EntityViewsRequestMsg entityViewsRequestMsg = 15; repeated AssetUpdateMsg assetUpdateMsg = 16; diff --git a/common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverter.java b/common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverter.java index 9226395289..312ae5b086 100644 --- a/common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverter.java +++ b/common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverter.java @@ -540,7 +540,7 @@ public class JsonConverter { public static Set convertToAttributes(JsonElement element) { long ts = System.currentTimeMillis(); - return new HashSet<>(parseValues(element.getAsJsonObject()).stream().map(kv -> new BaseAttributeKvEntry(kv, ts)).toList()); + return convertToAttributes(element, ts); } public static Set convertToAttributes(JsonElement element, long ts) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java index dd5d61e834..0767e5e303 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java @@ -96,6 +96,7 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi private final TbQueueAdmin edgeEventAdmin; private final AtomicLong consumerCount = new AtomicLong(); + private final AtomicLong edgeConsumerCount = new AtomicLong(); public KafkaMonolithQueueFactory(TopicService topicService, TbKafkaSettings kafkaSettings, TbServiceInfoProvider serviceInfoProvider, @@ -472,7 +473,7 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder> consumerBuilder = TbKafkaConsumerTemplate.builder(); consumerBuilder.settings(kafkaSettings); consumerBuilder.topic(topicService.buildTopicName("tb_edge_event.notifications." + tenantId + "." + edgeId)); - consumerBuilder.clientId("monolith-to-edge-event-consumer" + serviceInfoProvider.getServiceId()); + consumerBuilder.clientId("monolith-to-edge-event-consumer-" + serviceInfoProvider.getServiceId() + "-" + edgeConsumerCount.incrementAndGet()); consumerBuilder.groupId(topicService.buildTopicName("monolith-edge-event-consumer")); consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToEdgeEventNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); consumerBuilder.admin(edgeEventAdmin); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java index cc0e044917..06fd6147a8 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java @@ -95,6 +95,7 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory { private final TbQueueAdmin edgeEventAdmin; private final AtomicLong consumerCount = new AtomicLong(); + private final AtomicLong edgeConsumerCount = new AtomicLong(); public KafkaTbCoreQueueFactory(TopicService topicService, TbKafkaSettings kafkaSettings, @@ -421,7 +422,7 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory { TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder> consumerBuilder = TbKafkaConsumerTemplate.builder(); consumerBuilder.settings(kafkaSettings); consumerBuilder.topic(topicService.buildTopicName("tb_edge_event.notifications." + tenantId + "." + edgeId)); - consumerBuilder.clientId("tb-core-edge-event-consumer" + serviceInfoProvider.getServiceId()); + consumerBuilder.clientId("tb-core-edge-event-consumer-" + serviceInfoProvider.getServiceId() + "-" + edgeConsumerCount.incrementAndGet()); consumerBuilder.groupId(topicService.buildTopicName("tb-core-edge-event-consumer")); consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToEdgeEventNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); consumerBuilder.admin(edgeEventAdmin); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java index 713d5b9fa8..3146eba95b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java @@ -24,6 +24,9 @@ import org.eclipse.leshan.core.LwM2m; import org.eclipse.leshan.core.LwM2m.Version; import org.eclipse.leshan.core.link.Link; import org.eclipse.leshan.core.link.attributes.Attribute; +import org.eclipse.leshan.core.link.lwm2m.MixedLwM2mLink; +import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttribute; +import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.node.LwM2mMultipleResource; @@ -453,22 +456,33 @@ public class LwM2mClient { } private void setSupportedClientObjects(){ - this.supportedClientObjects = new ConcurrentHashMap<>(); - for (Link link: this.registration.getSortedObjectLinks()) { - LwM2mPath lwM2mPath = new LwM2mPath(link.getUriReference()); - if (lwM2mPath.isObject()) { - LwM2m.Version ver; - if (link.getAttributes().get("ver")!= null) { - ver = new LwM2m.Version(link.getAttributes().get("ver").getValue().toString()); - } else { - ver = getDefaultObjectIDVer(); + if (this.registration.getSupportedObject() != null && this.registration.getSupportedObject().size() > 0) { + this.supportedClientObjects = this.registration.getSupportedObject(); + } else { + this.supportedClientObjects = new ConcurrentHashMap<>(); + for (Link link : this.registration.getSortedObjectLinks()) { + if (link instanceof MixedLwM2mLink) { + LwM2mPath path = ((MixedLwM2mLink) link).getPath(); + // add supported objects + if (path.isObject() || path.isObjectInstance()) { + int objectId = path.getObjectId(); + LwM2mAttribute versionParamValue = link.getAttributes().get(LwM2mAttributes.OBJECT_VERSION); + if (versionParamValue != null) { + // if there is a version attribute then use it as version for this object + this.supportedClientObjects.put(objectId, versionParamValue.getValue()); + } else { + // there is no version attribute attached. + // In this case we use the DEFAULT_VERSION only if this object stored as supported object. + Version currentVersion = this.supportedClientObjects.get(objectId); + if (currentVersion == null) { + this.supportedClientObjects.put(objectId, getDefaultObjectIDVer()); + } + } + } } - this.supportedClientObjects.put(lwM2mPath.getObjectId(), ver); - } else if (lwM2mPath.getObjectId() != null && this.supportedClientObjects.get(lwM2mPath.getObjectId()) == null){ - this.supportedClientObjects.put(lwM2mPath.getObjectId(), getDefaultObjectIDVer()); } } } - } + diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDes.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDes.java index 6eb54e5447..6b2bff2467 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDes.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDes.java @@ -17,7 +17,6 @@ package org.thingsboard.server.transport.lwm2m.server.store.util; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; @@ -57,7 +56,7 @@ public class LwM2MClientSerDes { @SneakyThrows public static byte[] serialize(LwM2mClient client) { - JsonObject o = new JsonObject(); + JsonObject o = new JsonObject(); o.addProperty("nodeId", client.getNodeId()); o.addProperty("endpoint", client.getEndpoint()); @@ -111,14 +110,7 @@ public class LwM2MClientSerDes { o.addProperty("defaultObjectIDVer", client.getDefaultObjectIDVer().toString()); if (client.getRegistration() != null) { - String registrationAddress = client.getRegistration().getAddress().toString(); JsonNode registrationNode = registrationSerDes.jSerialize(client.getRegistration()); - if (!registrationAddress.equals(registrationNode.get("transportdata").get("address").asText())){ - ObjectNode actualRegAddress = (ObjectNode)registrationNode.get("transportdata"); - actualRegAddress.put("address", registrationAddress); - ObjectNode actualIdentity = (ObjectNode) actualRegAddress.get("identity"); - actualIdentity.put("address", registrationAddress); - } o.addProperty("registration", registrationNode.toString()); } o.addProperty("asleep", client.isAsleep()); @@ -188,7 +180,7 @@ public class LwM2MClientSerDes { case STRING: return value.getAsString(); case TIME: - return Instant.ofEpochMilli(value.getAsLong()); + return new Date(value.getAsLong()); case OBJLNK: return ObjectLink.decodeFromString(value.getAsString()); case UNSIGNED_INTEGER: @@ -249,7 +241,7 @@ public class LwM2MClientSerDes { o.addProperty(VALUE, ((ObjectLink) value).encodeToString()); break; case UNSIGNED_INTEGER: - o.addProperty(VALUE, Integer.toUnsignedString((int)value)); + o.addProperty(VALUE, Integer.toUnsignedString((int) value)); break; default: throw new LwM2mNodeException(String.format("Type %s is not supported", type.name())); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java index a79ed43784..d415b8c22a 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java @@ -279,6 +279,7 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl clientContext.unregister(client, registration); SessionInfoProto sessionInfo = client.getSession(); if (sessionInfo != null) { + securityStore.remove(client.getEndpoint(), client.getRegistration().getId()); sessionManager.deregister(sessionInfo); sessionStore.remove(registration.getEndpoint()); log.info("Client close session: [{}] unReg [{}] name [{}] profile ", registration.getId(), registration.getEndpoint(), sessionInfo.getDeviceType()); @@ -401,7 +402,6 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl .stream().filter(e -> e.getProfileId() != null) .filter(e -> e.getProfileId().equals(deviceProfile.getUuidId())).collect(Collectors.toList()); clients.forEach(client -> { - this.securityStore.remove(client.getEndpoint(), client.getRegistration().getId()); client.onDeviceProfileUpdate(deviceProfile); }); if (clients.size() > 0) { diff --git a/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDesTest.java b/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDesTest.java index 6b17225538..710b06a864 100644 --- a/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDesTest.java +++ b/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDesTest.java @@ -18,13 +18,17 @@ package org.thingsboard.server.transport.lwm2m.server.store.util; import org.eclipse.leshan.core.LwM2m.LwM2mVersion; import org.eclipse.leshan.core.endpoint.EndpointUriUtil; import org.eclipse.leshan.core.link.Link; +import org.eclipse.leshan.core.link.attributes.AttributeSet; +import org.eclipse.leshan.core.link.attributes.ResourceTypeAttribute; import org.eclipse.leshan.core.node.LwM2mMultipleResource; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.peer.IpPeer; import org.eclipse.leshan.core.request.WriteRequest; +import org.eclipse.leshan.server.registration.DefaultRegistrationDataExtractor; import org.eclipse.leshan.server.registration.Registration; +import org.eclipse.leshan.server.registration.RegistrationDataExtractor; import org.junit.jupiter.api.Test; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.device.data.PowerMode; @@ -83,12 +87,22 @@ public class LwM2MClientSerDesTest { client.init(credentialsResponse, UUID.randomUUID()); + AttributeSet attrs = new AttributeSet( // + new ResourceTypeAttribute("oma.lwm2m")); + + Link[] objs = new Link[]{new Link("/15", attrs), new Link("/17")}; + + RegistrationDataExtractor.RegistrationData dataFromObjectLinks = new DefaultRegistrationDataExtractor().extractDataFromObjectLinks(objs, + LwM2mVersion.V1_0); + Registration registration = new Registration .Builder("test", "endpoint", new IpPeer(new InetSocketAddress(Inet4Address.getLoopbackAddress(), 1000)), - EndpointUriUtil.createUri("coap://localhost:5685")) - .supportedContentFormats() - .supportedObjects(Map.of(15, LwM2mVersion.V1_0, 17, LwM2mVersion.V1_0)) - .objectLinks(new Link[] { new Link("/15"), new Link("/17") }) + EndpointUriUtil.createUri("coap://localhost:5685")) + .objectLinks(objs) + .rootPath(dataFromObjectLinks.getAlternatePath()) + .supportedContentFormats(dataFromObjectLinks.getSupportedContentFormats()) + .supportedObjects(dataFromObjectLinks.getSupportedObjects()) + .availableInstances(dataFromObjectLinks.getAvailableInstances()) .build(); client.setRegistration(registration); @@ -135,12 +149,7 @@ public class LwM2MClientSerDesTest { assertEquals(client.getPsmActivityTimer(), desClient.getPsmActivityTimer()); assertEquals(client.getPagingTransmissionWindow(), desClient.getPagingTransmissionWindow()); assertEquals(client.getEdrxCycle(), desClient.getEdrxCycle()); - if (((IpPeer)desClient.getRegistration().getClientTransportData()).getSocketAddress().isUnresolved()) { - String actualReg = desClient.getRegistration().toString().replaceAll("/", ""); - assertEquals(client.getRegistration().toString(), actualReg); - } else { - assertEquals(client.getRegistration(), desClient.getRegistration()); - } + assertEquals(client.getRegistration(), desClient.getRegistration()); assertEquals(client.isAsleep(), desClient.isAsleep()); assertEquals(client.getLastUplinkTime(), desClient.getLastUplinkTime()); assertEquals(client.getSleepTask(), desClient.getSleepTask()); diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/DefaultTransportMBeanConfiguration.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/DefaultTransportMBeanConfiguration.java new file mode 100644 index 0000000000..0bf8fdeede --- /dev/null +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/DefaultTransportMBeanConfiguration.java @@ -0,0 +1,49 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.mqtt; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jmx.export.MBeanExporter; +import org.thingsboard.server.common.transport.service.DefaultTransportService; +import org.thingsboard.server.queue.util.TbTransportComponent; + +import java.util.HashMap; +import java.util.Map; + +@Configuration +@TbTransportComponent +@RequiredArgsConstructor +public class DefaultTransportMBeanConfiguration { + + private final DefaultTransportService transportService; + + @Bean + public HashMapObserver hashMapObserver() { + return new HashMapObserver(transportService.sessions); + } + + @Bean + public MBeanExporter mBeanExporter() { + MBeanExporter exporter = new MBeanExporter(); + Map beans = new HashMap<>(); + beans.put("org.thingsboard:type=TransportSessionMapObserver", hashMapObserver()); + exporter.setBeans(beans); + return exporter; + } + +} diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/HashMapObserver.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/HashMapObserver.java new file mode 100644 index 0000000000..446ecb8aa4 --- /dev/null +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/HashMapObserver.java @@ -0,0 +1,188 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.mqtt; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.transport.service.SessionMetaData; +import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; +import org.thingsboard.server.transport.mqtt.session.GatewayDeviceSessionContext; + +import java.util.Map; +import java.util.UUID; + +@RequiredArgsConstructor +@Slf4j +public class HashMapObserver implements HashMapObserverMBean { + private final Map map; + + @Override + public int getSize() { + return map.size(); + } + + @Override + public long getGatewayCount(String unused) { + return map.values().stream().filter(v-> v.getSessionInfo() != null && v.getSessionInfo().getIsGateway()).count(); + } + + @Override + public long getNonGatewayCount(String unused) { + return map.values().stream().filter(v-> v.getSessionInfo() != null && !v.getSessionInfo().getIsGateway()).count(); + } + + @Override + public String getSessionByUUID(String uuid) { + return String.valueOf(map.get(UUID.fromString(uuid))); + } + + void addContent(Object entry, int count, StringBuilder content) { + String lineContent = String.valueOf(entry).replaceAll(System.lineSeparator()," "); + log.info("{} content = {}", count, lineContent); + content.append(lineContent).append(System.lineSeparator()); + } + + @Override + public String getAllSessions(String unused) { + log.info("getAllSessions()"); + StringBuilder content = new StringBuilder(); + try { + int count = 0; + for (Map.Entry entry : map.entrySet()) { + addContent(entry, ++count, content); + } + return content.toString(); + } catch (Exception e) { + log.error(e.getMessage(), e); + throw e; + } + } + + @Override + public String getSubscribedSessions(String unused) { + log.info("getSubscribedSessions()"); + StringBuilder content = new StringBuilder(); + try { + int count = 0; + for (Map.Entry entry : map.entrySet()) { + boolean hasSubscription = entry.getValue().isSubscribedToRPC() || entry.getValue().isSubscribedToAttributes(); + if (hasSubscription) { + addContent(entry, ++count, content); + } + } + return content.toString(); + } catch (Exception e) { + log.error(e.getMessage(), e); + throw e; + } + } + + @Override + public String getNonActiveSessions(String unused) { + log.info("getNonActiveSessions()"); + StringBuilder content = new StringBuilder(); + try { + int count = 0; + for (Map.Entry entry : map.entrySet()) { + SessionMetaData sessionMetaData = entry.getValue(); + if (sessionMetaData.getListener() instanceof MqttTransportHandler) { + MqttTransportHandler listener = (MqttTransportHandler) sessionMetaData.getListener(); + if (!listener.deviceSessionCtx.getChannel().channel().isActive()) { + addContent(entry, ++count, content); + } + } + } + return content.toString(); + } catch (Exception e) { + log.error(e.getMessage(), e); + throw e; + } + } + + @Override + public String getActiveSessions(String unused) { + log.info("getActiveSessions()"); + StringBuilder content = new StringBuilder(); + try { + int count = 0; + for (Map.Entry entry : map.entrySet()) { + SessionMetaData sessionMetaData = entry.getValue(); + if (sessionMetaData.getListener() instanceof MqttTransportHandler) { + MqttTransportHandler listener = (MqttTransportHandler) sessionMetaData.getListener(); + if (listener.deviceSessionCtx.getChannel().channel().isActive()) { + addContent(entry, ++count, content); + } + } else { + addContent(sessionMetaData.getListener().getClass(), ++count, content); + } + } + return content.toString(); + } catch (Exception e) { + log.error(e.getMessage(), e); + + throw e; + } + } + + @Override + public String getGatewayDeviceSessionContextConnectedSessions(String unused) { + log.info("getGatewayDeviceSessionContextConnectedSessions()"); + StringBuilder content = new StringBuilder(); + try { + int count = 0; + for (Map.Entry entry : map.entrySet()) { + SessionMetaData sessionMetaData = entry.getValue(); + if (sessionMetaData.getListener() instanceof GatewayDeviceSessionContext) { + GatewayDeviceSessionContext listener = (GatewayDeviceSessionContext) sessionMetaData.getListener(); + if (listener.isConnected()) { + addContent(entry, ++count, content); + } + } + } + addContent(count, count, content); + return content.toString(); + } catch (Exception e) { + log.error(e.getMessage(), e); + throw e; + } + } + @Override + public String getDeviceAwareSessionContextNotConnectedSessions(String unused) { + log.info("getDeviceAwareSessionContextNotConnectedSessions()"); + StringBuilder content = new StringBuilder(); + try { + int count = 0; + for (Map.Entry entry : map.entrySet()) { + SessionMetaData sessionMetaData = entry.getValue(); + if (sessionMetaData.getListener() instanceof DeviceAwareSessionContext) { + DeviceAwareSessionContext listener = (DeviceAwareSessionContext) sessionMetaData.getListener(); + if (!listener.isConnected()) { + addContent(entry, ++count, content); + } + } + } + addContent(count, count, content); + return content.toString(); + } catch (Exception e) { + log.error(e.getMessage(), e); + throw e; + } + } + +} + +// 4a7d85c9-eb4b-4fbc-8f6c-deb158cc9ac7=SessionMetaData(sessionInfo=nodeId: "bestia.local" sessionIdMSB: 5367593433178001340 sessionIdLSB: -8111863975520724281 tenantIdMSB: -1954197196874116625 tenantIdLSB: -7022192637061768255 deviceIdMSB: -5222516332438875665 deviceIdLSB: -7338416368958642691 deviceName: "Demo Device" deviceType: "default" gwSessionIdMSB: 3730140660294699909 gwSessionIdLSB: -7918622346767288875 deviceProfileIdMSB: -1952135612572036625 deviceProfileIdLSB: -7022192637061768255 customerIdMSB: 1405474927960789426 customerIdLSB: -9187201950435737472 gatewayIdMSB: 164837549830312431 gatewayIdLSB: -7338416368958642691 , sessionType=ASYNC, listener=GatewayDeviceSessionContext(super=AbstractGatewayDeviceSessionContext(super=MqttDeviceAwareSessionContext(super=DeviceAwareSessionContext(sessionId=4a7d85c9-eb4b-4fbc-8f6c-deb158cc9ac7, deviceId=b785e510-d34e-11ef-9a28-b5316a4ee5fd, tenantId=e4e14c00-d341-11ef-9e8c-29007391dbc1, deviceInfo=TransportDeviceInfo(tenantId=e4e14c00-d341-11ef-9e8c-29007391dbc1, customerId=13814000-1dd2-11b2-8080-808080808080, deviceProfileId=e4e89f00-d341-11ef-9e8c-29007391dbc1, deviceId=b785e510-d34e-11ef-9a28-b5316a4ee5fd, deviceName=Demo Device, deviceType=default, powerMode=null, additionalInfo={"lastConnectedGateway":"02499ee0-d348-11ef-9a28-b5316a4ee5fd"}, edrxCycle=null, psmActivityTimer=null, pagingTransmissionWindow=null, gateway=false), deviceProfile=DeviceProfile(tenantId=e4e14c00-d341-11ef-9e8c-29007391dbc1, name=default, description=Default device profile, isDefault=true, type=DEFAULT, transportType=DEFAULT, provisionType=DISABLED, defaultRuleChainId=null, defaultDashboardId=null, defaultQueueName=null, profileData=DeviceProfileData(configuration=DefaultDeviceProfileConfiguration(), transportConfiguration=DefaultDeviceProfileTransportConfiguration(), provisionConfiguration=DisabledDeviceProfileProvisionConfiguration(provisionDeviceSecret=null), alarms=null), provisionDeviceKey=null, firmwareId=null, softwareId=null, defaultEdgeRuleChainId=null, externalId=null, version=1), sessionInfo=nodeId: "bestia.local" sessionIdMSB: 5367593433178001340 sessionIdLSB: -8111863975520724281 tenantIdMSB: -1954197196874116625 tenantIdLSB: -7022192637061768255 deviceIdMSB: -5222516332438875665 deviceIdLSB: -7338416368958642691 deviceName: "Demo Device" deviceType: "default" gwSessionIdMSB: 3730140660294699909 gwSessionIdLSB: -7918622346767288875 deviceProfileIdMSB: -1952135612572036625 deviceProfileIdLSB: -7022192637061768255 customerIdMSB: 1405474927960789426 customerIdLSB: -9187201950435737472 gatewayIdMSB: 164837549830312431 gatewayIdLSB: -7338416368958642691 , connected=true), mqttQoSMap={org.thingsboard.server.transport.mqtt.session.MqttTopicMatcher@697a7658=1, org.thingsboard.server.transport.mqtt.session.MqttTopicMatcher@d44c5be8=1, org.thingsboard.server.transport.mqtt.session.MqttTopicMatcher@e3a8acd0=1, org.thingsboard.server.transport.mqtt.session.MqttTopicMatcher@e3d2681a=1, org.thingsboard.server.transport.mqtt.session.MqttTopicMatcher@a65b8616=1, org.thingsboard.server.transport.mqtt.session.MqttTopicMatcher@133a8264=1, org.thingsboard.server.transport.mqtt.session.MqttTopicMatcher@12e92606=1}), parent=org.thingsboard.server.transport.mqtt.session.GatewaySessionHandler@3d4885c2, transportService=org.thingsboard.server.common.transport.service.DefaultTransportService@77c16f87)), scheduledFuture=null, subscribedToAttributes=true, subscribedToRPC=true, overwriteActivityTime=false) +//1 \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/queue/QueueMsgConstructor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/HashMapObserverMBean.java similarity index 52% rename from application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/queue/QueueMsgConstructor.java rename to common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/HashMapObserverMBean.java index 1aad30fb8f..3a70674aec 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/queue/QueueMsgConstructor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/HashMapObserverMBean.java @@ -13,17 +13,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.edge.rpc.constructor.queue; +package org.thingsboard.server.transport.mqtt; -import org.thingsboard.server.common.data.id.QueueId; -import org.thingsboard.server.common.data.queue.Queue; -import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.service.edge.rpc.constructor.MsgConstructor; +public interface HashMapObserverMBean { + int getSize(); -public interface QueueMsgConstructor extends MsgConstructor { + long getGatewayCount(String unused); - QueueUpdateMsg constructQueueUpdatedMsg(UpdateMsgType msgType, Queue queue); + long getNonGatewayCount(String unused); - QueueUpdateMsg constructQueueDeleteMsg(QueueId queueId); + String getSessionByUUID(String key); + + String getAllSessions(String key); + + String getSubscribedSessions(String unused); + + String getNonActiveSessions(String unused); + + String getActiveSessions(String unused); + + String getGatewayDeviceSessionContextConnectedSessions(String unused); + + String getDeviceAwareSessionContextNotConnectedSessions(String unused); } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index e669016514..e6efcf8206 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -963,7 +963,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement unSubResults.add((short) MqttReasonCodes.UnsubAck.NO_SUBSCRIPTION_EXISTED.byteValue()); } } - if (!activityReported) { + if (!activityReported && !deviceSessionCtx.isProvisionOnly()) { transportService.recordActivity(deviceSessionCtx.getSessionInfo()); } ctx.writeAndFlush(createUnSubAckMessage(mqttMsg.variableHeader().messageId(), unSubResults)); diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewayDeviceSessionContext.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewayDeviceSessionContext.java index e174ad86bd..1570bb4b7f 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewayDeviceSessionContext.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewayDeviceSessionContext.java @@ -17,6 +17,8 @@ package org.thingsboard.server.transport.mqtt.session; import io.netty.channel.ChannelFuture; import io.netty.handler.codec.mqtt.MqttMessage; +import lombok.Getter; +import lombok.ToString; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.id.DeviceId; @@ -34,9 +36,11 @@ import java.util.concurrent.ConcurrentMap; /** * Created by ashvayka on 19.01.17. */ +@ToString(callSuper = true) @Slf4j public abstract class AbstractGatewayDeviceSessionContext extends MqttDeviceAwareSessionContext implements SessionMsgListener { + @Getter protected final T parent; private final TransportService transportService; diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java index 2e205ff840..8713e79988 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java @@ -16,8 +16,10 @@ package org.thingsboard.server.transport.mqtt.session; import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.SettableFuture; import com.google.gson.JsonArray; import com.google.gson.JsonElement; @@ -115,6 +117,7 @@ public abstract class AbstractGatewaySessionHandler devices; private final ConcurrentMap> deviceFutures; protected final ConcurrentMap mqttQoSMap; + @Getter protected final ChannelHandlerContext channel; protected final DeviceSessionCtx deviceSessionCtx; protected final GatewayMetricsService gatewayMetricsService; @@ -124,6 +127,7 @@ public abstract class AbstractGatewaySessionHandler { + Futures.addCallback(future, new FutureCallback() { + @Override + public void onSuccess(T result) { + log.debug("[{}] Gateway disconnect [{}] device deregister callback [{}]", gateway.getTenantId(), gateway.getDeviceId(), name); + deregisterSession(name, result); + } + + @Override + public void onFailure(Throwable t) { + + } + }, MoreExecutors.directExecutor()); + }); + + devices.forEach(this::deregisterSession); + } catch (Exception e) { + log.error("Gateway disconnect failure", e); + } } public void onDeviceDeleted(String deviceName) { diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionContext.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionContext.java index dff4245566..c5af48fadb 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionContext.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionContext.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.mqtt.session; +import lombok.ToString; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; @@ -24,6 +25,7 @@ import java.util.concurrent.ConcurrentMap; /** * Created by nickAS21 on 26.12.22 */ +@ToString(callSuper = true) public class GatewayDeviceSessionContext extends AbstractGatewayDeviceSessionContext { public GatewayDeviceSessionContext(GatewaySessionHandler parent, diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/MqttDeviceAwareSessionContext.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/MqttDeviceAwareSessionContext.java index 8ff3580753..ba8c8cb9fa 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/MqttDeviceAwareSessionContext.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/MqttDeviceAwareSessionContext.java @@ -16,6 +16,7 @@ package org.thingsboard.server.transport.mqtt.session; import io.netty.handler.codec.mqtt.MqttQoS; +import lombok.ToString; import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; import java.util.List; @@ -27,6 +28,7 @@ import java.util.stream.Collectors; /** * Created by ashvayka on 30.08.18. */ +@ToString(callSuper = true) public abstract class MqttDeviceAwareSessionContext extends DeviceAwareSessionContext { private final ConcurrentMap mqttQoSMap; diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java index 04f113b918..66c14ca8ee 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java @@ -67,6 +67,7 @@ import org.thingsboard.server.transport.snmp.session.DeviceSessionContext; import org.thingsboard.server.transport.snmp.session.ScheduledTask; import java.io.IOException; +import java.net.InetAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -97,8 +98,10 @@ public class SnmpTransportService implements TbTransportService, CommandResponde private final Map responseDataMappers = new EnumMap<>(SnmpCommunicationSpec.class); private final Map responseProcessors = new EnumMap<>(SnmpCommunicationSpec.class); - @Value("${transport.snmp.bind_port:1620}") + @Value("${transport.snmp.bind_port:0}") private Integer snmpBindPort; + @Value("${transport.snmp.bind_address:0.0.0.0}") + private String snmpBindAddress; @Value("${transport.snmp.response_processing.parallelism_level:4}") private int responseProcessingThreadPoolSize; @Value("${transport.snmp.scheduler_thread_pool_size:4}") @@ -134,10 +137,10 @@ public class SnmpTransportService implements TbTransportService, CommandResponde TransportMapping transportMapping; switch (snmpUnderlyingProtocol) { case "udp": - transportMapping = new DefaultUdpTransportMapping(new UdpAddress(snmpBindPort)); + transportMapping = new DefaultUdpTransportMapping(new UdpAddress(InetAddress.getByName(snmpBindAddress), snmpBindPort)); break; case "tcp": - transportMapping = new DefaultTcpTransportMapping(new TcpAddress(snmpBindPort)); + transportMapping = new DefaultTcpTransportMapping(new TcpAddress(InetAddress.getByName(snmpBindAddress), snmpBindPort)); break; default: throw new IllegalArgumentException("Underlying protocol " + snmpUnderlyingProtocol + " for SNMP is not supported"); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index af9dca8583..547eab6172 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -735,7 +735,11 @@ public class DefaultTransportService extends TransportActivityManager implements } private void recordActivityInternal(TransportProtos.SessionInfoProto sessionInfo) { - onActivity(toSessionId(sessionInfo), sessionInfo, getCurrentTimeMillis()); + if (sessionInfo != null) { + onActivity(toSessionId(sessionInfo), sessionInfo, getCurrentTimeMillis()); + } else { + log.warn("Session info is missing, unable to record activity"); + } } @Override diff --git a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java index 8498b4a7e4..365fe23079 100644 --- a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java +++ b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java @@ -94,6 +94,10 @@ import java.util.function.Function; import java.util.stream.Collectors; import static org.eclipse.jgit.api.ListBranchCommand.ListMode; +import static org.eclipse.jgit.transport.RemoteRefUpdate.Status.REJECTED_NODELETE; +import static org.eclipse.jgit.transport.RemoteRefUpdate.Status.REJECTED_NONFASTFORWARD; +import static org.eclipse.jgit.transport.RemoteRefUpdate.Status.REJECTED_OTHER_REASON; +import static org.eclipse.jgit.transport.RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED; @Slf4j public class GitRepository { @@ -358,8 +362,10 @@ public class GitRepository { result.forEach(pushResult -> { for (RemoteRefUpdate update : pushResult.getRemoteUpdates()) { RemoteRefUpdate.Status status = update.getStatus(); - if (status != RemoteRefUpdate.Status.OK && status != RemoteRefUpdate.Status.UP_TO_DATE) { - throw new RuntimeException("Failed to push changes: " + Optional.ofNullable(update.getMessage()).orElseGet(status::name)); + if (status == REJECTED_NONFASTFORWARD || status == REJECTED_NODELETE || + status == REJECTED_REMOTE_CHANGED || status == REJECTED_OTHER_REASON) { + throw new RuntimeException("Remote repository answered with error: " + + Optional.ofNullable(update.getMessage()).orElseGet(status::name)); } } }); @@ -444,7 +450,7 @@ public class GitRepository { } ObjectId result = git.getRepository().resolve(rev); if (result == null) { - throw new IllegalArgumentException("Failed to parse git revision string: \"" + rev + "\""); + throw new IllegalArgumentException("Failed to resolve '" + rev + "'"); } return result; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/mobile/QrCodeSettingServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/mobile/QrCodeSettingServiceImpl.java index ee3e59c60b..27cc3eebbf 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/mobile/QrCodeSettingServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/mobile/QrCodeSettingServiceImpl.java @@ -115,7 +115,7 @@ public class QrCodeSettingServiceImpl extends AbstractCachedEntityService { mobile.setAppSecret(appSecret); mobile.setPlatformType(platformType); mobile.setStatus(status); - mobile.setVersionInfo(fromJson(versionInfo, MobileAppVersionInfo.class)); - mobile.setStoreInfo(fromJson(storeInfo, StoreInfo.class)); + mobile.setVersionInfo(versionInfo != null ? fromJson(versionInfo, MobileAppVersionInfo.class) : MOBILE_APP_VERSION_INFO_EMPTY_OBJECT); + mobile.setStoreInfo(storeInfo != null ? fromJson(storeInfo, StoreInfo.class) : MOBILE_APP_STORE_INFO_EMPTY_OBJECT); return mobile; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetTypeInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetTypeInfoEntity.java index 7a9e7f35bc..d718e5613d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetTypeInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetTypeInfoEntity.java @@ -15,17 +15,22 @@ */ package org.thingsboard.server.dao.model.sql; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; import io.hypersistence.utils.hibernate.type.array.StringArrayType; import jakarta.persistence.Column; +import jakarta.persistence.Convert; import jakarta.persistence.Entity; import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; import org.hibernate.annotations.Immutable; import org.hibernate.annotations.Type; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.widget.BaseWidgetType; import org.thingsboard.server.common.data.widget.WidgetTypeInfo; import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.util.mapping.JsonConverter; import java.util.HashMap; import java.util.Map; @@ -58,6 +63,10 @@ public class WidgetTypeInfoEntity extends AbstractWidgetTypeEntity() {})); return widgetTypeInfo; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java index 16088e83c9..1a4c3ffab2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java @@ -372,25 +372,6 @@ public class DefaultNotifications { .build()) .build(); - public static final DefaultNotification queueTypeDeprecation = DefaultNotification.builder() - .name("Queue type deprecation") - .type(NotificationType.GENERAL) - .subject("WARNING: ${queueType} deprecation") - .text("Starting with ThingsBoard 4.0, ${queueType} will no longer be supported as a message queue for microservices. " + - "Please migrate to Apache Kafka. This change will not impact any rule nodes.") - .icon("warning").color(RED_COLOR) - .build(); - - public static final DefaultNotification databaseTypeDeprecation = DefaultNotification.builder() - .name("Database type deprecation") - .type(NotificationType.GENERAL) - .subject("WARNING: ${databaseType} deprecation") - .text("Starting with ThingsBoard 4.0, ${databaseType} will no longer be supported as a storage provider. " + - "Please migrate to Cassandra or PostgreSQL.") - .icon("warning") - .color(RED_COLOR) - .build(); - private final NotificationTemplateService templateService; private final NotificationRuleService ruleService; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/MobileAppDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/MobileAppDataValidator.java index d91ea8251d..68d9de859b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/MobileAppDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/MobileAppDataValidator.java @@ -30,21 +30,17 @@ public class MobileAppDataValidator extends DataValidator { @Override protected void validateDataImpl(TenantId tenantId, MobileApp mobileApp) { - if (mobileApp.getPlatformType() == PlatformType.ANDROID) { - if (mobileApp.getStoreInfo() != null && + if (mobileApp.getStatus() == MobileAppStatus.PUBLISHED) { + if (mobileApp.getStoreInfo() == null) { + throw new DataValidationException("Store info is required for published apps"); + } + if (mobileApp.getPlatformType() == PlatformType.ANDROID && (mobileApp.getStoreInfo().getSha256CertFingerprints() == null || mobileApp.getStoreInfo().getStoreLink() == null)) { throw new DataValidationException("Sha256CertFingerprints and store link are required"); - } - } else if (mobileApp.getPlatformType() == PlatformType.IOS) { - if (mobileApp.getStoreInfo() != null && + } else if (mobileApp.getPlatformType() == PlatformType.IOS && (mobileApp.getStoreInfo().getAppId() == null || mobileApp.getStoreInfo().getStoreLink() == null)) { throw new DataValidationException("AppId and store link are required"); } - } else { - throw new DataValidationException("Wrong application platform type"); - } - if (mobileApp.getStatus() == MobileAppStatus.PUBLISHED && mobileApp.getStoreInfo() == null) { - throw new DataValidationException("Store info is required for published apps"); } } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDao.java index 6220d9b717..4a656c3374 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDao.java @@ -84,6 +84,11 @@ public class JpaWidgetTypeDao extends JpaAbstractDao findSystemWidgetTypes(WidgetTypeFilter widgetTypeFilter, PageLink pageLink) { boolean deprecatedFilterEnabled = !DeprecatedFilter.ALL.equals(widgetTypeFilter.getDeprecatedFilter()); diff --git a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeDao.java b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeDao.java index d5876bbbd9..e61976b6dd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeDao.java @@ -56,6 +56,8 @@ public interface WidgetTypeDao extends Dao, ExportableEntityD boolean existsByTenantIdAndId(TenantId tenantId, UUID widgetTypeId); + WidgetTypeInfo findWidgetTypeInfoById(TenantId tenantId, UUID widgetTypeId); + PageData findSystemWidgetTypes(WidgetTypeFilter widgetTypeFilter, PageLink pageLink); PageData findAllTenantWidgetTypesByTenantId(WidgetTypeFilter widgetTypeFilter, PageLink pageLink); diff --git a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java index a1e241073f..1b0ecb33a4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java @@ -87,6 +87,13 @@ public class WidgetTypeServiceImpl implements WidgetTypeService { return widgetTypeDao.findById(tenantId, widgetTypeId.getId()); } + @Override + public WidgetTypeInfo findWidgetTypeInfoById(TenantId tenantId, WidgetTypeId widgetTypeId) { + log.trace("Executing findWidgetTypeInfoById [{}]", widgetTypeId); + Validator.validateId(widgetTypeId, id -> "Incorrect widgetTypeId " + id); + return widgetTypeDao.findWidgetTypeInfoById(tenantId, widgetTypeId.getId()); + } + @Override public boolean widgetTypeExistsByTenantIdAndWidgetTypeId(TenantId tenantId, WidgetTypeId widgetTypeId) { log.trace("Executing widgetTypeExistsByTenantIdAndWidgetTypeId, tenantId [{}], widgetTypeId [{}]", tenantId, widgetTypeId); diff --git a/dao/src/main/resources/sql/schema-views-and-functions.sql b/dao/src/main/resources/sql/schema-views-and-functions.sql index 31219ced18..b394742bcd 100644 --- a/dao/src/main/resources/sql/schema-views-and-functions.sql +++ b/dao/src/main/resources/sql/schema-views-and-functions.sql @@ -288,8 +288,15 @@ $$; DROP VIEW IF EXISTS widget_type_info_view CASCADE; CREATE OR REPLACE VIEW widget_type_info_view AS -SELECT t.* - , COALESCE((t.descriptor::json->>'type')::text, '') as widget_type +SELECT t.*, + COALESCE((t.descriptor::json->>'type')::text, '') as widget_type, + array_to_json(ARRAY( + SELECT json_build_object('id', wb.widgets_bundle_id, 'name', b.title) + FROM widgets_bundle_widget wb + JOIN widgets_bundle b ON wb.widgets_bundle_id = b.id + WHERE wb.widget_type_id = t.id + ORDER BY b.title + )) AS bundles FROM widget_type t; CREATE OR REPLACE PROCEDURE cleanup_timeseries_by_ttl(IN null_uuid uuid, diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDaoTest.java index 8010eab4cd..27376964f5 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDaoTest.java @@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.common.data.widget.BaseWidgetType; import org.thingsboard.server.common.data.widget.DeprecatedFilter; +import org.thingsboard.server.common.data.widget.WidgetBundleInfo; import org.thingsboard.server.common.data.widget.WidgetType; import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetTypeFilter; @@ -49,11 +50,10 @@ import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; -/** - * Created by Valerii Sosliuk on 4/30/2017. - */ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { // given search text should find a widget with tags, when searching by tags @@ -78,7 +78,7 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { final String BUNDLE_ALIAS = "BUNDLE_ALIAS"; final int WIDGET_TYPE_COUNT = 3; - List widgetTypeList; + List widgetTypeList; WidgetsBundle widgetsBundle; @Autowired @@ -107,7 +107,7 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { widgetTypeList.sort(Comparator.comparing(BaseWidgetType::getName)); } - WidgetTypeDetails createAndSaveWidgetType(TenantId tenantId, int number) { + WidgetTypeInfo createAndSaveWidgetType(TenantId tenantId, int number) { WidgetTypeDetails widgetType = new WidgetTypeDetails(); widgetType.setTenantId(tenantId); widgetType.setName("WIDGET_TYPE_" + number); @@ -117,9 +117,12 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { var descriptor = JacksonUtil.newObjectNode(); descriptor.put("type", number % 2 == 0 ? "latest" : "static"); widgetType.setDescriptor(descriptor); - String[] tags = new String[]{"Tag1_"+number, "Tag2_"+number, "TEST_"+number}; + String[] tags = new String[]{"Tag1_" + number, "Tag2_" + number, "TEST_" + number}; widgetType.setTags(tags); - return widgetTypeDao.save(TenantId.SYS_TENANT_ID, widgetType); + WidgetTypeDetails saved = widgetTypeDao.save(TenantId.SYS_TENANT_ID, widgetType); + List bundles = new ArrayList<>(); + bundles.add(new WidgetBundleInfo(widgetsBundle.getUuidId(), widgetsBundle.getName())); + return new WidgetTypeInfo(saved, bundles); } WidgetTypeDetails createAndSaveWidgetType(TenantId tenantId, int number, String[] tags) { @@ -139,7 +142,7 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { @After public void tearDown() { widgetsBundleDao.removeById(TenantId.SYS_TENANT_ID, widgetsBundle.getUuidId()); - for (WidgetType widgetType : widgetTypeList) { + for (WidgetTypeInfo widgetType : widgetTypeList) { widgetTypeDao.removeById(TenantId.SYS_TENANT_ID, widgetType.getUuidId()); } } @@ -160,7 +163,7 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { .widgetTypes(Collections.singletonList("static")).build(), new PageLink(1024, 0, "TYPE_DESCRIPTION", new SortOrder("createdTime"))); assertEquals(1, widgetTypes.getData().size()); - assertEquals(new WidgetTypeInfo(widgetTypeList.get(1)), widgetTypes.getData().get(0)); + assertEquals(widgetTypeList.get(1), widgetTypes.getData().get(0)); widgetTypes = widgetTypeDao.findSystemWidgetTypes( WidgetTypeFilter.builder() @@ -170,7 +173,7 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { .widgetTypes(Collections.emptyList()).build(), new PageLink(1024, 0, "hfgfd tag2_2 ghg", new SortOrder("createdTime"))); assertEquals(1, widgetTypes.getData().size()); - assertEquals(new WidgetTypeInfo(widgetTypeList.get(2)), widgetTypes.getData().get(0)); + assertEquals(widgetTypeList.get(2), widgetTypes.getData().get(0)); } @Test @@ -181,17 +184,17 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { Thread.sleep(2); var widgetType = saveWidgetType(TenantId.SYS_TENANT_ID, "widgetName"); sameNameList.add(widgetType); - widgetTypeList.add(widgetType); + widgetTypeList.add(new WidgetTypeInfo(widgetType)); } sameNameList.sort(Comparator.comparing(BaseWidgetType::getName).thenComparing((BaseWidgetType baseWidgetType) -> baseWidgetType.getId().getId())); List expected = sameNameList.stream().map(WidgetTypeInfo::new).collect(Collectors.toList()); PageData widgetTypesFirstPage = widgetTypeDao.findSystemWidgetTypes( WidgetTypeFilter.builder() - .tenantId(TenantId.SYS_TENANT_ID) - .fullSearch(true) - .deprecatedFilter(DeprecatedFilter.ALL) - .widgetTypes(Collections.singletonList("static")).build(), + .tenantId(TenantId.SYS_TENANT_ID) + .fullSearch(true) + .deprecatedFilter(DeprecatedFilter.ALL) + .widgetTypes(Collections.singletonList("static")).build(), new PageLink(10, 0, null, new SortOrder("name"))); assertEquals(10, widgetTypesFirstPage.getData().size()); assertThat(widgetTypesFirstPage.getData()).containsExactlyElementsOf(expected.subList(0, 10)); @@ -216,10 +219,10 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { PageData widgetTypes = widgetTypeDao.findSystemWidgetTypes( WidgetTypeFilter.builder() - .tenantId(TenantId.SYS_TENANT_ID) - .fullSearch(true) - .deprecatedFilter(DeprecatedFilter.ALL) - .widgetTypes(null).build(), + .tenantId(TenantId.SYS_TENANT_ID) + .fullSearch(true) + .deprecatedFilter(DeprecatedFilter.ALL) + .widgetTypes(null).build(), new PageLink(10, 0, searchText) ); @@ -254,12 +257,12 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { public void testFindTenantWidgetTypesByTenantId() { UUID tenantId = Uuids.timeBased(); for (int i = 0; i < WIDGET_TYPE_COUNT; i++) { - var widgetType = createAndSaveWidgetType(new TenantId(tenantId), i); + var widgetType = createAndSaveWidgetType(TenantId.fromUUID(tenantId), i); widgetTypeList.add(widgetType); } PageData widgetTypes = widgetTypeDao.findTenantWidgetTypesByTenantId( WidgetTypeFilter.builder() - .tenantId(new TenantId(tenantId)) + .tenantId(TenantId.fromUUID(tenantId)) .fullSearch(true) .deprecatedFilter(DeprecatedFilter.ALL) .widgetTypes(null).build(), @@ -360,16 +363,16 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { @Test public void testFindByWidgetTypeInfosByBundleId() { - PageData widgetTypes = widgetTypeDao.findWidgetTypesInfosByWidgetsBundleId(TenantId.SYS_TENANT_ID.getId(), widgetsBundle.getUuidId(),true, DeprecatedFilter.ALL, Collections.singletonList("latest"), + PageData widgetTypes = widgetTypeDao.findWidgetTypesInfosByWidgetsBundleId(TenantId.SYS_TENANT_ID.getId(), widgetsBundle.getUuidId(), true, DeprecatedFilter.ALL, Collections.singletonList("latest"), new PageLink(1024, 0, "TYPE_DESCRIPTION", new SortOrder("createdTime"))); assertEquals(2, widgetTypes.getData().size()); - assertEquals(new WidgetTypeInfo(widgetTypeList.get(0)), widgetTypes.getData().get(0)); - assertEquals(new WidgetTypeInfo(widgetTypeList.get(2)), widgetTypes.getData().get(1)); + assertEquals(widgetTypeList.get(0), widgetTypes.getData().get(0)); + assertEquals(widgetTypeList.get(2), widgetTypes.getData().get(1)); widgetTypes = widgetTypeDao.findWidgetTypesInfosByWidgetsBundleId(TenantId.SYS_TENANT_ID.getId(), widgetsBundle.getUuidId(), true, DeprecatedFilter.ALL, Collections.emptyList(), new PageLink(1024, 0, "hfgfd TEST_0 ghg", new SortOrder("createdTime"))); assertEquals(1, widgetTypes.getData().size()); - assertEquals(new WidgetTypeInfo(widgetTypeList.get(0)), widgetTypes.getData().get(0)); + assertEquals(widgetTypeList.get(0), widgetTypes.getData().get(0)); } @Test @@ -410,7 +413,7 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { @Test public void testFindByTenantIdAndFqn() { - WidgetType result = widgetTypeList.get(0); + WidgetTypeInfo result = widgetTypeList.get(0); assertNotNull(result); WidgetType widgetType = widgetTypeDao.findByTenantIdAndFqn(TenantId.SYS_TENANT_ID.getId(), "FQN_0"); assertEquals(result.getId(), widgetType.getId()); @@ -426,7 +429,7 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { @Test public void testFindByImageLink() { - TenantId tenantId = new TenantId(UUID.randomUUID()); + TenantId tenantId = TenantId.fromUUID(UUID.randomUUID()); WidgetTypeDetails details = createAndSaveWidgetType(tenantId, 0, new String[]{"a"}); details.setDescriptor(JacksonUtil.newObjectNode().put("bg", "/image/tenant/widget.png")); widgetTypeDao.save(tenantId, details); @@ -437,14 +440,82 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { widgetTypeDao.removeById(tenantId, details.getUuidId()); } + @Test + public void testFindWidgetTypesWithBundles() { + PageData widgetTypes = widgetTypeDao.findWidgetTypesInfosByWidgetsBundleId( + TenantId.SYS_TENANT_ID.getId(), + widgetsBundle.getUuidId(), + true, + DeprecatedFilter.ALL, + Collections.singletonList("latest"), + new PageLink(1024, 0, "TYPE_DESCRIPTION", new SortOrder("createdTime")) + ); + + assertEquals(2, widgetTypes.getData().size()); + for (var widgetType : widgetTypes.getData()) { + assertFalse("Bundles should not be empty", widgetType.getBundles().isEmpty()); + assertEquals(BUNDLE_ALIAS, widgetType.getBundles().get(0).getName()); + } + } + + @Test + public void testAddWidgetTypeToNewBundleAndVerifyBundles() { + String newBundleTitle = "New Bundle Title"; + WidgetsBundle newWidgetsBundle = new WidgetsBundle(); + newWidgetsBundle.setAlias("NewBundle"); + newWidgetsBundle.setTitle(newBundleTitle); + newWidgetsBundle.setId(new WidgetsBundleId(UUID.randomUUID())); + newWidgetsBundle.setTenantId(TenantId.SYS_TENANT_ID); + newWidgetsBundle = widgetsBundleDao.save(TenantId.SYS_TENANT_ID, newWidgetsBundle); + + for (int i = 0; i < widgetTypeList.size(); i++) { + WidgetTypeInfo widgetType = widgetTypeList.get(i); + widgetTypeDao.saveWidgetsBundleWidget(new WidgetsBundleWidget(newWidgetsBundle.getId(), widgetType.getId(), i)); + } + + PageData widgetTypes = widgetTypeDao.findWidgetTypesInfosByWidgetsBundleId( + TenantId.SYS_TENANT_ID.getId(), + newWidgetsBundle.getUuidId(), + true, + DeprecatedFilter.ALL, + Collections.singletonList("latest"), + new PageLink(1024, 0, "TYPE_DESCRIPTION", new SortOrder("createdTime")) + ); + + assertEquals(2, widgetTypes.getData().size()); + WidgetTypeInfo widgetTypeInfo1 = widgetTypes.getData().get(0); + WidgetTypeInfo widgetTypeInfo2 = widgetTypes.getData().get(1); + + assertEquals(2, widgetTypeInfo1.getBundles().size()); + assertTrue("Bundles should contain 'BUNDLE_ALIAS'", widgetTypeInfo1.getBundles().stream().anyMatch(bundle -> BUNDLE_ALIAS.equals(bundle.getName()))); + assertTrue("Bundles should contain 'New Bundle Title'", widgetTypeInfo1.getBundles().stream().anyMatch(bundle -> newBundleTitle.equals(bundle.getName()))); + + assertEquals(2, widgetTypeInfo2.getBundles().size()); + assertTrue("Bundles should contain 'BUNDLE_ALIAS'", widgetTypeInfo2.getBundles().stream().anyMatch(bundle -> "BUNDLE_ALIAS".equals(bundle.getName()))); + assertTrue("Bundles should contain 'New Bundle Title'", widgetTypeInfo2.getBundles().stream().anyMatch(bundle -> newBundleTitle.equals(bundle.getName()))); + + // cleanup and verify + widgetsBundleDao.removeById(newWidgetsBundle.getTenantId(), newWidgetsBundle.getUuidId()); + widgetTypes = widgetTypeDao.findWidgetTypesInfosByWidgetsBundleId( + TenantId.SYS_TENANT_ID.getId(), + newWidgetsBundle.getUuidId(), + true, + DeprecatedFilter.ALL, + Collections.singletonList("latest"), + new PageLink(1024, 0, "TYPE_DESCRIPTION", new SortOrder("createdTime")) + ); + widgetTypes.getData().forEach(widgetTypeInfo -> assertEquals(1, widgetTypeInfo.getBundles().size())); + } + private WidgetTypeDetails saveWidgetType(TenantId tenantId, String name) { WidgetTypeDetails widgetType = new WidgetTypeDetails(); widgetType.setTenantId(tenantId); widgetType.setDescription("WIDGET_TYPE_DESCRIPTION" + StringUtils.randomAlphabetic(7)); widgetType.setName(name); var descriptor = JacksonUtil.newObjectNode(); - descriptor.put("type","static"); + descriptor.put("type", "static"); widgetType.setDescriptor(descriptor); return widgetTypeDao.save(TenantId.SYS_TENANT_ID, widgetType); } + } diff --git a/docker/docker-compose.prometheus-grafana.yml b/docker/docker-compose.prometheus-grafana.yml index 311d96bd86..f6a5f7fb6d 100644 --- a/docker/docker-compose.prometheus-grafana.yml +++ b/docker/docker-compose.prometheus-grafana.yml @@ -23,7 +23,7 @@ volumes: services: prometheus: - image: prom/prometheus:v2.1.0 + image: prom/prometheus:v3.1.0 volumes: - ./monitoring/prometheus/:/etc/prometheus/ - prometheus_data:/prometheus diff --git a/monitoring/src/main/resources/root_rule_chain.json b/monitoring/src/main/resources/root_rule_chain.json index ff44ebfe79..46bdc72d9f 100644 --- a/monitoring/src/main/resources/root_rule_chain.json +++ b/monitoring/src/main/resources/root_rule_chain.json @@ -21,9 +21,13 @@ "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode", "name": "Save Timeseries", "singletonMode": false, - "configurationVersion": 0, + "configurationVersion": 1, "configuration": { - "defaultTTL": 0 + "defaultTTL": 0, + "useServerTs": false, + "processingSettings": { + "type": "ON_EVERY_MESSAGE" + } }, "externalId": null }, @@ -273,9 +277,13 @@ "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode", "name": "Save Timeseries", "singletonMode": false, - "configurationVersion": 0, + "configurationVersion": 1, "configuration": { - "defaultTTL": 0 + "defaultTTL": 0, + "useServerTs": false, + "processingSettings": { + "type": "ON_EVERY_MESSAGE" + } }, "externalId": null }, @@ -307,11 +315,13 @@ "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode", "name": "Save Timeseries with TTL", "singletonMode": false, - "configurationVersion": 0, + "configurationVersion": 1, "configuration": { "defaultTTL": 180, - "skipLatestPersistence": null, - "useServerTs": null + "useServerTs": false, + "processingSettings": { + "type": "ON_EVERY_MESSAGE" + } }, "externalId": null } @@ -415,4 +425,4 @@ ], "ruleChainConnections": null } -} \ No newline at end of file +} diff --git a/msa/black-box-tests/src/test/resources/MqttRuleNodeTestMetadata.json b/msa/black-box-tests/src/test/resources/MqttRuleNodeTestMetadata.json index c2bb52514e..c09da6aebb 100644 --- a/msa/black-box-tests/src/test/resources/MqttRuleNodeTestMetadata.json +++ b/msa/black-box-tests/src/test/resources/MqttRuleNodeTestMetadata.json @@ -36,11 +36,13 @@ "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode", "name": "save timeseries", "singletonMode": false, - "configurationVersion": 0, + "configurationVersion": 1, "configuration": { "defaultTTL": 0, - "skipLatestPersistence": false, - "useServerTs": false + "useServerTs": false, + "processingSettings": { + "type": "ON_EVERY_MESSAGE" + } }, "externalId": null }, diff --git a/packaging/java/scripts/control/template.service b/packaging/java/scripts/control/template.service index 235c3a1700..f4fa806c32 100644 --- a/packaging/java/scripts/control/template.service +++ b/packaging/java/scripts/control/template.service @@ -8,7 +8,8 @@ ExecStart=${pkg.installFolder}/bin/${pkg.name}.jar SuccessExitStatus=143 # Service shall be restarted in 30 seconds when the service process exits, is killed, or a timeout is reached. -Restart=always # default 'no' +# Restart parameter default value 'no' +Restart=always RestartSec=30 [Install] 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 b8fb9b61ff..74c98cdec5 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 @@ -36,7 +36,7 @@ public interface NotificationCenter { NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest, FutureCallback callback); - void sendGeneralWebNotification(TenantId tenantId, UsersFilter recipients, NotificationTemplate template, GeneralNotificationInfo info); + void sendGeneralWebNotification(TenantId tenantId, UsersFilter recipients, NotificationTemplate template, GeneralNotificationInfo info); // for future use void sendSystemNotification(TenantId tenantId, NotificationTargetId targetId, NotificationType type, NotificationInfo info); // for future use and compatibility with PE diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TimeseriesSaveRequest.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TimeseriesSaveRequest.java index 2b5881212d..fb667fbfb2 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TimeseriesSaveRequest.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TimeseriesSaveRequest.java @@ -38,10 +38,18 @@ public class TimeseriesSaveRequest { private final EntityId entityId; private final List entries; private final long ttl; - private final boolean saveLatest; - private final boolean onlyLatest; + private final Strategy strategy; private final FutureCallback callback; + public record Strategy(boolean saveTimeseries, boolean saveLatest, boolean sendWsUpdate) { + + public static final Strategy SAVE_ALL = new Strategy(true, true, true); + public static final Strategy WS_ONLY = new Strategy(false, false, true); + public static final Strategy LATEST_AND_WS = new Strategy(false, true, true); + public static final Strategy SKIP_ALL = new Strategy(false, false, false); + + } + public static Builder builder() { return new Builder(); } @@ -53,9 +61,8 @@ public class TimeseriesSaveRequest { private EntityId entityId; private List entries; private long ttl; + private Strategy strategy = Strategy.SAVE_ALL; private FutureCallback callback; - private boolean saveLatest = true; - private boolean onlyLatest; Builder() {} @@ -92,14 +99,8 @@ public class TimeseriesSaveRequest { return this; } - public Builder saveLatest(boolean saveLatest) { - this.saveLatest = saveLatest; - return this; - } - - public Builder onlyLatest(boolean onlyLatest) { - this.onlyLatest = onlyLatest; - this.saveLatest = true; + public Builder strategy(Strategy strategy) { + this.strategy = strategy; return this; } @@ -123,7 +124,7 @@ public class TimeseriesSaveRequest { } public TimeseriesSaveRequest build() { - return new TimeseriesSaveRequest(tenantId, customerId, entityId, entries, ttl, saveLatest, onlyLatest, callback); + return new TimeseriesSaveRequest(tenantId, customerId, entityId, entries, ttl, strategy, callback); } } diff --git a/rule-engine/rule-engine-api/src/test/java/org/thingsboard/rule/engine/api/TimeseriesSaveRequestTest.java b/rule-engine/rule-engine-api/src/test/java/org/thingsboard/rule/engine/api/TimeseriesSaveRequestTest.java new file mode 100644 index 0000000000..321892991e --- /dev/null +++ b/rule-engine/rule-engine-api/src/test/java/org/thingsboard/rule/engine/api/TimeseriesSaveRequestTest.java @@ -0,0 +1,51 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.api; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class TimeseriesSaveRequestTest { + + @Test + void testDefaultSaveStrategyIsSaveAll() { + var request = TimeseriesSaveRequest.builder().build(); + + assertThat(request.getStrategy()).isEqualTo(TimeseriesSaveRequest.Strategy.SAVE_ALL); + } + + @Test + void testSaveAllStrategy() { + assertThat(TimeseriesSaveRequest.Strategy.SAVE_ALL).isEqualTo(new TimeseriesSaveRequest.Strategy(true, true, true)); + } + + @Test + void testWsOnlyStrategy() { + assertThat(TimeseriesSaveRequest.Strategy.WS_ONLY).isEqualTo(new TimeseriesSaveRequest.Strategy(false, false, true)); + } + + @Test + void testLatestAndWsStrategy() { + assertThat(TimeseriesSaveRequest.Strategy.LATEST_AND_WS).isEqualTo(new TimeseriesSaveRequest.Strategy(false, true, true)); + } + + @Test + void testSkipAllStrategy() { + assertThat(TimeseriesSaveRequest.Strategy.SKIP_ALL).isEqualTo(new TimeseriesSaveRequest.Strategy(false, false, false)); + } + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAssignToCustomerNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAssignToCustomerNode.java index 01dd176896..d6afd971b4 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAssignToCustomerNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAssignToCustomerNode.java @@ -40,7 +40,6 @@ import org.thingsboard.server.common.msg.TbMsg; nodeDescription = "Assign message originator entity to customer", nodeDetails = "Finds target customer by title and assign message originator entity to this customer. " + "Rule node will create a new customer if it doesn't exist, and 'Create new customer if it doesn't exist' enabled.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeAssignToCustomerConfig", icon = "add_circle", version = 1 diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNode.java index 622448f494..55ba8ae59a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNode.java @@ -43,7 +43,6 @@ import org.thingsboard.server.common.msg.TbMsg; "If alarm was not cleared, original message is returned. Otherwise new Message returned with type 'ALARM', Alarm object in 'msg' property and 'metadata' will contains 'isClearedAlarm' property. " + "Message payload can be accessed via msg property. For example 'temperature = ' + msg.temperature ;. " + "Message metadata can be accessed via metadata property. For example 'name = ' + metadata.customerName;.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeClearAlarmConfig", icon = "notifications_off" ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java index a4b0227551..1a1bcf7daf 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java @@ -63,7 +63,6 @@ import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCES nodeDetails = "Copy attributes from asset/device to related entity view according to entity view configuration. \n " + "Copy will be done only for attributes that are between start and end dates and according to attribute keys configuration. \n" + "Changes message originator to related entity view and produces new messages according to count of updated entity views", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbNodeEmptyConfig", icon = "content_copy" ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java index 7e535596cd..c1e8ae89aa 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java @@ -51,7 +51,6 @@ import java.util.List; "If alarm was not created, original message is returned. Otherwise new Message returned with type 'ALARM', Alarm object in 'msg' property and 'metadata' will contains one of those properties 'isNewAlarm/isExistingAlarm'. " + "Message payload can be accessed via msg property. For example 'temperature = ' + msg.temperature ;. " + "Message metadata can be accessed via metadata property. For example 'name = ' + metadata.customerName;.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeCreateAlarmConfig", icon = "notifications_active" ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNode.java index 7571695b49..0ae90cd6a7 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNode.java @@ -61,7 +61,6 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback; "Useful in GPS tracking use cases where relation acts as a temporary indicator of a tracker presence in specific geofence." + "
  • Change originator to target entity - useful when you need to process submitted message as a message from target entity.
  • " + "Output connections: Success - if the relation already exists or successfully created, otherwise Failure.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeCreateRelationConfig", icon = "add_circle", version = 1 diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNode.java index dbc914065f..abbaad7cb3 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNode.java @@ -53,7 +53,6 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback; "
  • User - use a user with the specified email as the target entity to delete relation with.
  • " + "
  • Edge - use an edge with the specified name as the target entity to delete relation with.
  • " + "Output connections: Success - If the relation(s) successfully deleted, otherwise Failure.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeDeleteRelationConfig", icon = "remove_circle", version = 1 diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java index 7925178b33..b00023e59d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java @@ -58,7 +58,6 @@ import java.util.Set; "This node is particularly useful when device isn't using transports to receive data, such as when fetching data from external API or computing new data within the rule chain.", configClazz = TbDeviceStateNodeConfiguration.class, relationTypes = {TbNodeConnectionType.SUCCESS, TbNodeConnectionType.FAILURE, "Rate limited"}, - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeDeviceStateConfig" ) public class TbDeviceStateNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java index 6b07d41a42..cdbaca491e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java @@ -43,7 +43,6 @@ import java.util.Objects; nodeDetails = "Transform incoming Message with configured JS function to String and log final value into Thingsboard log file. " + "Message payload can be accessed via msg property. For example 'temperature = ' + msg.temperature ;. " + "Message metadata can be accessed via metadata property. For example 'name = ' + metadata.customerName;.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeLogConfig", icon = "menu" ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java index edbd9c7ca4..b387877f07 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java @@ -42,7 +42,6 @@ import java.util.concurrent.atomic.AtomicLong; nodeDescription = "Count incoming messages", nodeDetails = "Count incoming messages for specified interval and produces POST_TELEMETRY_REQUEST msg with messages count", icon = "functions", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeMsgCountConfig" ) public class TbMsgCountNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbSaveToCustomCassandraTableNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbSaveToCustomCassandraTableNode.java index a0dbc5da97..5c485353ab 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbSaveToCustomCassandraTableNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbSaveToCustomCassandraTableNode.java @@ -69,7 +69,6 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback; "Note:If the mapping key is $entity_id, that is identified by the Message Originator, then to the appropriate column name(mapping value) will be write the message originator id.

    " + "If specified message field does not exist or is not a JSON Primitive, the outbound message will be routed via failure chain," + " otherwise, the message will be routed via success chain.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeCustomTableConfig", icon = "file_upload", ruleChainTypes = RuleChainType.CORE) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbUnassignFromCustomerNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbUnassignFromCustomerNode.java index 93fbe8019e..9569d0be77 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbUnassignFromCustomerNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbUnassignFromCustomerNode.java @@ -42,7 +42,6 @@ import org.thingsboard.server.common.msg.TbMsg; "If the incoming message originator is a dashboard, will try to search for the customer by title specified in the configuration. " + "If customer doesn't exist, the exception will be thrown. Otherwise will unassign the dashboard from retrieved customer.

    " + "Other entities can be assigned only to one customer, so specified customer title in the configuration will be ignored if the originator isn't a dashboard.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeUnAssignToCustomerConfig", icon = "remove_circle", version = 1 diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/lambda/TbAwsLambdaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/lambda/TbAwsLambdaNode.java index bb9f5416db..6cf01f80c7 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/lambda/TbAwsLambdaNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/lambda/TbAwsLambdaNode.java @@ -53,7 +53,6 @@ import static org.thingsboard.server.dao.service.ConstraintValidator.validateFie "It sends messages using a RequestResponse invocation type. " + "The node uses a pre-configured client and specified function to run.

    " + "Output connections: Success, Failure.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbExternalNodeLambdaConfig", iconUrl = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjQ4IiBoZWlnaHQ9IjQ4Ij48cGF0aCBkPSJNMTMuMjMgMTAuNTZWMTBjLTEuOTQgMC0zLjk5LjM5LTMuOTkgMi42NyAwIDEuMTYuNjEgMS45NSAxLjYzIDEuOTUuNzYgMCAxLjQzLS40NyAxLjg2LTEuMjIuNTItLjkzLjUtMS44LjUtMi44NG0yLjcgNi41M2MtLjE4LjE2LS40My4xNy0uNjMuMDYtLjg5LS43NC0xLjA1LTEuMDgtMS41NC0xLjc5LTEuNDcgMS41LTIuNTEgMS45NS00LjQyIDEuOTUtMi4yNSAwLTQuMDEtMS4zOS00LjAxLTQuMTcgMC0yLjE4IDEuMTctMy42NCAyLjg2LTQuMzggMS40Ni0uNjQgMy40OS0uNzYgNS4wNC0uOTNWNy41YzAtLjY2LjA1LTEuNDEtLjMzLTEuOTYtLjMyLS40OS0uOTUtLjctMS41LS43LTEuMDIgMC0xLjkzLjUzLTIuMTUgMS42MS0uMDUuMjQtLjI1LjQ4LS40Ny40OWwtMi42LS4yOGMtLjIyLS4wNS0uNDYtLjIyLS40LS41Ni42LTMuMTUgMy40NS00LjEgNi00LjEgMS4zIDAgMyAuMzUgNC4wMyAxLjMzQzE3LjExIDQuNTUgMTcgNi4xOCAxNyA3Ljk1djQuMTdjMCAxLjI1LjUgMS44MSAxIDIuNDguMTcuMjUuMjEuNTQgMCAuNzFsLTIuMDYgMS43OGgtLjAxIj48L3BhdGg+PHBhdGggZD0iTTIwLjE2IDE5LjU0QzE4IDIxLjE0IDE0LjgyIDIyIDEyLjEgMjJjLTMuODEgMC03LjI1LTEuNDEtOS44NS0zLjc2LS4yLS4xOC0uMDItLjQzLjI1LS4yOSAyLjc4IDEuNjMgNi4yNSAyLjYxIDkuODMgMi42MSAyLjQxIDAgNS4wNy0uNSA3LjUxLTEuNTMuMzctLjE2LjY2LjI0LjMyLjUxIj48L3BhdGg+PHBhdGggZD0iTTIxLjA3IDE4LjVjLS4yOC0uMzYtMS44NS0uMTctMi41Ny0uMDgtLjE5LjAyLS4yMi0uMTYtLjAzLS4zIDEuMjQtLjg4IDMuMjktLjYyIDMuNTMtLjMzLjI0LjMtLjA3IDIuMzUtMS4yNCAzLjMyLS4xOC4xNi0uMzUuMDctLjI2LS4xMS4yNi0uNjcuODUtMi4xNC41Ny0yLjV6Ij48L3BhdGg+PC9zdmc+" ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNode.java index 2c732150e1..3a9733ee82 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNode.java @@ -46,7 +46,6 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback; nodeDetails = "Will publish message payload to the AWS SNS topic. Outbound message will contain response fields " + "(messageId, requestId) in the Message Metadata from the AWS SNS. " + "For example requestId field can be accessed with metadata.requestId.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbExternalNodeSnsConfig", iconUrl = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjQ4IiBoZWlnaHQ9IjQ4Ij48cGF0aCBkPSJNMTMuMjMgMTAuNTZWMTBjLTEuOTQgMC0zLjk5LjM5LTMuOTkgMi42NyAwIDEuMTYuNjEgMS45NSAxLjYzIDEuOTUuNzYgMCAxLjQzLS40NyAxLjg2LTEuMjIuNTItLjkzLjUtMS44LjUtMi44NG0yLjcgNi41M2MtLjE4LjE2LS40My4xNy0uNjMuMDYtLjg5LS43NC0xLjA1LTEuMDgtMS41NC0xLjc5LTEuNDcgMS41LTIuNTEgMS45NS00LjQyIDEuOTUtMi4yNSAwLTQuMDEtMS4zOS00LjAxLTQuMTcgMC0yLjE4IDEuMTctMy42NCAyLjg2LTQuMzggMS40Ni0uNjQgMy40OS0uNzYgNS4wNC0uOTNWNy41YzAtLjY2LjA1LTEuNDEtLjMzLTEuOTYtLjMyLS40OS0uOTUtLjctMS41LS43LTEuMDIgMC0xLjkzLjUzLTIuMTUgMS42MS0uMDUuMjQtLjI1LjQ4LS40Ny40OWwtMi42LS4yOGMtLjIyLS4wNS0uNDYtLjIyLS40LS41Ni42LTMuMTUgMy40NS00LjEgNi00LjEgMS4zIDAgMyAuMzUgNC4wMyAxLjMzQzE3LjExIDQuNTUgMTcgNi4xOCAxNyA3Ljk1djQuMTdjMCAxLjI1LjUgMS44MSAxIDIuNDguMTcuMjUuMjEuNTQgMCAuNzFsLTIuMDYgMS43OGgtLjAxIj48L3BhdGg+PHBhdGggZD0iTTIwLjE2IDE5LjU0QzE4IDIxLjE0IDE0LjgyIDIyIDEyLjEgMjJjLTMuODEgMC03LjI1LTEuNDEtOS44NS0zLjc2LS4yLS4xOC0uMDItLjQzLjI1LS4yOSAyLjc4IDEuNjMgNi4yNSAyLjYxIDkuODMgMi42MSAyLjQxIDAgNS4wNy0uNSA3LjUxLTEuNTMuMzctLjE2LjY2LjI0LjMyLjUxIj48L3BhdGg+PHBhdGggZD0iTTIxLjA3IDE4LjVjLS4yOC0uMzYtMS44NS0uMTctMi41Ny0uMDgtLjE5LjAyLS4yMi0uMTYtLjAzLS4zIDEuMjQtLjg4IDMuMjktLjYyIDMuNTMtLjMzLjI0LjMtLjA3IDIuMzUtMS4yNCAzLjMyLS4xOC4xNi0uMzUuMDctLjI2LS4xMS4yNi0uNjcuODUtMi4xNC41Ny0yLjV6Ij48L3BhdGg+PC9zdmc+" ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java index e61cd537c4..d9b6120fc7 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java @@ -52,7 +52,6 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback; "response fields (messageId, requestId, messageBodyMd5, messageAttributesMd5" + ", sequenceNumber) in the Message Metadata from the AWS SQS." + " For example requestId field can be accessed with metadata.requestId.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbExternalNodeSqsConfig", iconUrl = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjQ4IiBoZWlnaHQ9IjQ4Ij48cGF0aCBkPSJNMTMuMjMgMTAuNTZWMTBjLTEuOTQgMC0zLjk5LjM5LTMuOTkgMi42NyAwIDEuMTYuNjEgMS45NSAxLjYzIDEuOTUuNzYgMCAxLjQzLS40NyAxLjg2LTEuMjIuNTItLjkzLjUtMS44LjUtMi44NG0yLjcgNi41M2MtLjE4LjE2LS40My4xNy0uNjMuMDYtLjg5LS43NC0xLjA1LTEuMDgtMS41NC0xLjc5LTEuNDcgMS41LTIuNTEgMS45NS00LjQyIDEuOTUtMi4yNSAwLTQuMDEtMS4zOS00LjAxLTQuMTcgMC0yLjE4IDEuMTctMy42NCAyLjg2LTQuMzggMS40Ni0uNjQgMy40OS0uNzYgNS4wNC0uOTNWNy41YzAtLjY2LjA1LTEuNDEtLjMzLTEuOTYtLjMyLS40OS0uOTUtLjctMS41LS43LTEuMDIgMC0xLjkzLjUzLTIuMTUgMS42MS0uMDUuMjQtLjI1LjQ4LS40Ny40OWwtMi42LS4yOGMtLjIyLS4wNS0uNDYtLjIyLS40LS41Ni42LTMuMTUgMy40NS00LjEgNi00LjEgMS4zIDAgMyAuMzUgNC4wMyAxLjMzQzE3LjExIDQuNTUgMTcgNi4xOCAxNyA3Ljk1djQuMTdjMCAxLjI1LjUgMS44MSAxIDIuNDguMTcuMjUuMjEuNTQgMCAuNzFsLTIuMDYgMS43OGgtLjAxIj48L3BhdGg+PHBhdGggZD0iTTIwLjE2IDE5LjU0QzE4IDIxLjE0IDE0LjgyIDIyIDEyLjEgMjJjLTMuODEgMC03LjI1LTEuNDEtOS44NS0zLjc2LS4yLS4xOC0uMDItLjQzLjI1LS4yOSAyLjc4IDEuNjMgNi4yNSAyLjYxIDkuODMgMi42MSAyLjQxIDAgNS4wNy0uNSA3LjUxLTEuNTMuMzctLjE2LjY2LjI0LjMyLjUxIj48L3BhdGg+PHBhdGggZD0iTTIxLjA3IDE4LjVjLS4yOC0uMzYtMS44NS0uMTctMi41Ny0uMDgtLjE5LjAyLS4yMi0uMTYtLjAzLS4zIDEuMjQtLjg4IDMuMjktLjYyIDMuNTMtLjMzLjI0LjMtLjA3IDIuMzUtMS4yNCAzLjMyLS4xOC4xNi0uMzUuMDctLjI2LS4xMS4yNi0uNjcuODUtMi4xNC41Ny0yLjV6Ij48L3BhdGg+PC9zdmc+" ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java index c7bf1423f8..0fcabd806a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java @@ -62,7 +62,6 @@ import static org.thingsboard.server.common.data.DataConstants.QUEUE_NAME; nodeDescription = "Periodically generates messages", nodeDetails = "Generates messages with configurable period. Javascript function used for message generation.", inEnabled = false, - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeGeneratorConfig", icon = "repeat" ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java index cf4b5b4ce1..6d13332f6f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java @@ -59,8 +59,7 @@ import static org.thingsboard.server.common.data.DataConstants.QUEUE_NAME; "
  • ALL - return all messages as a single JSON array message. " + "Where each element represents object with msg and metadata inner properties.
  • ", icon = "content_copy", - uiResources = {"static/rulenode/rulenode-core-config.js"}, - configDirective = "tbActionNodeMsgDeduplicationConfig" + configDirective = "tbTransformationNodeDeduplicationConfig" ) @Slf4j public class TbMsgDeduplicationNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java index a8a2c4e9b5..eb18bd13fe 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java @@ -45,7 +45,6 @@ import java.util.concurrent.TimeUnit; "Deprecated because the acknowledged message still stays in memory (to be delayed) and this " + "does not guarantee that message will be processed even if the \"retry failures and timeouts\" processing strategy will be chosen.", icon = "pause", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeMsgDelayConfig" ) public class TbMsgDelayNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToCloudNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToCloudNode.java index 1ed2f8e849..50355632a3 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToCloudNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToCloudNode.java @@ -46,7 +46,6 @@ import java.util.UUID; "
    ALARM

    " + "Message will be routed via Failure route if node was not able to save cloud event to database or unsupported originator type/message type arrived. " + "In case successful storage cloud event to database message will be routed via Success route.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodePushToCloudConfig", icon = "cloud_upload", ruleChainTypes = RuleChainType.EDGE diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNode.java index 4ae86742a2..2659bb281e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNode.java @@ -61,7 +61,6 @@ import static org.thingsboard.server.dao.edge.BaseRelatedEdgesService.RELATED_ED "
    ALARM

    " + "Message will be routed via Failure route if node was not able to save edge event to database or unsupported message type arrived. " + "In case successful storage edge event to database message will be routed via Success route.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodePushToEdgeConfig", icon = "cloud_download", ruleChainTypes = RuleChainType.CORE diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNode.java index 0fc03be83b..b0f5552b72 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNode.java @@ -36,7 +36,6 @@ import org.thingsboard.server.common.data.plugin.ComponentType; nodeDescription = "Route incoming messages based on the name of the asset profile", nodeDetails = "Route incoming messages based on the name of the asset profile. The asset profile name is case-sensitive.

    " + "Output connections: Asset profile name or Failure", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbNodeEmptyConfig") public class TbAssetTypeSwitchNode extends TbAbstractTypeSwitchNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java index ed703e8661..6aa05007d1 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java @@ -43,7 +43,6 @@ import java.util.Objects; nodeDescription = "Checks alarm status.", nodeDetails = "Checks the alarm status to match one of the specified statuses.

    " + "Output connections: True, False, Failure.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeCheckAlarmStatusConfig") public class TbCheckAlarmStatusNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckMessageNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckMessageNode.java index 1b9f2f2a78..0ca3f74d29 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckMessageNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckMessageNode.java @@ -40,7 +40,6 @@ import java.util.Map; nodeDetails = "By default, the rule node checks that all specified fields are present. " + "Uncheck the 'Check that all selected fields are present' if the presence of at least one field is sufficient.

    " + "Output connections: True, False, Failure", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeCheckMessageConfig") public class TbCheckMessageNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java index 0356548a03..f24312dba9 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java @@ -56,7 +56,6 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback; "Otherwise, the rule node checks the presence of a relation to any entity. " + "In both cases, relation lookup is based on configured direction and type.

    " + "Output connections: True, False, Failure", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeCheckRelationConfig") public class TbCheckRelationNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNode.java index d440969fff..a7ec1e86a4 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNode.java @@ -36,7 +36,6 @@ import org.thingsboard.server.common.data.plugin.ComponentType; nodeDescription = "Route incoming messages based on the name of the device profile", nodeDetails = "Route incoming messages based on the name of the device profile. The device profile name is case-sensitive

    " + "Output connections: Device profile name or Failure", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbNodeEmptyConfig") public class TbDeviceTypeSwitchNode extends TbAbstractTypeSwitchNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java index 6d5aaa6477..767c2f91d6 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java @@ -44,7 +44,6 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback; "Message metadata can be accessed via metadata property. For example metadata.customerName === 'John';
    " + "Message type can be accessed via msgType property.

    " + "Output connections: True, False, Failure", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeScriptConfig" ) public class TbJsFilterNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java index e866f05bd0..10277d067b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java @@ -46,7 +46,6 @@ import java.util.Set; "Message metadata can be accessed via metadata property. For example metadata.customerName === 'John';
    " + "Message type can be accessed via msgType property.

    " + "Output connections: Custom connection(s) defined by switch node or Failure", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeSwitchConfig") public class TbJsSwitchNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java index 579b6a3ec7..d818c3a8f1 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java @@ -38,7 +38,6 @@ import org.thingsboard.server.common.msg.TbMsg; nodeDescription = "Filter incoming messages by Message Type", nodeDetails = "If incoming message type is expected - send Message via True chain, otherwise False chain is used.

    " + "Output connections: True, False, Failure", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeMessageTypeConfig") public class TbMsgTypeFilterNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java index 971ab22588..455bf2f78c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java @@ -36,7 +36,6 @@ import org.thingsboard.server.common.msg.TbMsg; nodeDetails = "Sends messages with message types \"Post attributes\", \"Post telemetry\", \"RPC Request\"" + " etc. via corresponding chain, otherwise Other chain is used.

    " + "Output connections: Message type connection, Other - if message type is custom or Failure", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbNodeEmptyConfig") public class TbMsgTypeSwitchNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java index 94a13be583..ad873666c0 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java @@ -36,7 +36,6 @@ import org.thingsboard.server.common.msg.TbMsg; nodeDescription = "Filter incoming messages by the type of message originator entity", nodeDetails = "Checks that the entity type of the incoming message originator matches one of the values specified in the filter.

    " + "Output connections: True, False, Failure", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeOriginatorTypeConfig") public class TbOriginatorTypeFilterNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNode.java index 2eef991aa0..95f062e88f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNode.java @@ -31,7 +31,6 @@ import org.thingsboard.server.common.data.plugin.ComponentType; nodeDescription = "Route incoming messages by Message Originator Type", nodeDetails = "Routes messages to chain according to the entity type ('Device', 'Asset', etc.).

    " + "Output connections: Message originator type or Failure", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbNodeEmptyConfig") public class TbOriginatorTypeSwitchNode extends TbAbstractTypeSwitchNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbAckNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbAckNode.java index 505a5b3215..b19488f0ca 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbAckNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbAckNode.java @@ -33,7 +33,6 @@ import org.thingsboard.server.common.msg.TbMsg; configClazz = EmptyNodeConfiguration.class, nodeDescription = "Acknowledges the incoming message", nodeDetails = "After acknowledgement, the message is pushed to related rule nodes. Useful if you don't care what happens to this message next.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbNodeEmptyConfig" ) public class TbAckNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbCheckpointNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbCheckpointNode.java index cd827c9e54..8b9f113621 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbCheckpointNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbCheckpointNode.java @@ -40,7 +40,6 @@ import static org.thingsboard.server.common.data.DataConstants.QUEUE_NAME; hasQueueName = true, nodeDescription = "transfers the message to another queue", nodeDetails = "After successful transfer incoming message is automatically acknowledged. Queue name is configurable.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbNodeEmptyConfig" ) public class TbCheckpointNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbRuleChainInputNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbRuleChainInputNode.java index 0b30d02842..a594f548e8 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbRuleChainInputNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbRuleChainInputNode.java @@ -46,7 +46,6 @@ import java.util.UUID; "then target rule chain might be resolved dynamically based on incoming message originator. " + "In this case rule chain specified in the configuration will be used as fallback rule chain.

    " + "Output connections: Any connection(s) produced by output node(s) in the target rule chain.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFlowNodeRuleChainInputConfig", relationTypes = {}, ruleChainNode = true, diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbRuleChainOutputNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbRuleChainOutputNode.java index 2879397ace..1d887aa3a0 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbRuleChainOutputNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbRuleChainOutputNode.java @@ -34,7 +34,6 @@ import org.thingsboard.server.common.msg.TbMsg; nodeDetails = "Produces output of the rule chain processing. " + "The output is forwarded to the caller rule chain, as an output of the corresponding \"input\" rule node. " + "The output rule node name corresponds to the relation type of the output message, and it is used to forward messages to other rule nodes in the caller rule chain. ", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFlowNodeRuleChainOutputConfig", outEnabled = false ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/gcp/pubsub/TbPubSubNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/gcp/pubsub/TbPubSubNode.java index d6d3bb3c11..45fe4f8bc0 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/gcp/pubsub/TbPubSubNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/gcp/pubsub/TbPubSubNode.java @@ -52,7 +52,6 @@ import java.util.concurrent.TimeUnit; nodeDetails = "Will publish message payload to the Google Cloud Platform PubSub topic. Outbound message will contain response fields " + "(messageId in the Message Metadata from the GCP PubSub. " + "messageId field can be accessed with metadata.messageId.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbExternalNodePubSubConfig", iconUrl = "data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjgiIGhlaWdodD0iMTI4IiB2aWV3Qm94PSIwIDAgMTI4IDEyOCI+Cjx0aXRsZT5DbG91ZCBQdWJTdWI8L3RpdGxlPgo8Zz4KPHBhdGggZD0iTTEyNi40Nyw1OC4xMmwtMjYuMy00NS43NEExMS41NiwxMS41NiwwLDAsMCw5MC4zMSw2LjVIMzcuN2ExMS41NSwxMS41NSwwLDAsMC05Ljg2LDUuODhMMS41Myw1OGExMS40OCwxMS40OCwwLDAsMCwwLDExLjQ0bDI2LjMsNDZhMTEuNzcsMTEuNzcsMCwwLDAsOS44Niw2LjA5SDkwLjNhMTEuNzMsMTEuNzMsMCwwLDAsOS44Ny02LjA2bDI2LjMtNDUuNzRBMTEuNzMsMTEuNzMsMCwwLDAsMTI2LjQ3LDU4LjEyWiIgc3R5bGU9ImZpbGw6ICM3MzViMmYiLz4KPHBhdGggZD0iTTg5LjIyLDQ3Ljc0LDgzLjM2LDQ5bC0xNC42LTE0LjZMNjQuMDksNDMuMSw2MS41NSw1My4ybDQuMjksNC4yOUw1Ny42LDU5LjE4LDQ2LjMsNDcuODhsLTcuNjcsNy4zOEw1Mi43Niw2OS4zN2wtMTUsMTEuOUw3OCwxMjEuNUg5MC4zYTExLjczLDExLjczLDAsMCwwLDkuODctNi4wNmwyMC43Mi0zNloiIHN0eWxlPSJvcGFjaXR5OiAwLjA3MDAwMDAwMDI5ODAyMztpc29sYXRpb246IGlzb2xhdGUiLz4KPHBhdGggZD0iTTgyLjg2LDQ3YTUuMzIsNS4zMiwwLDEsMS0xLjk1LDcuMjdBNS4zMiw1LjMyLDAsMCwxLDgyLjg2LDQ3IiBzdHlsZT0iZmlsbDogI2ZmZiIvPgo8cGF0aCBkPSJNMzkuODIsNTYuMThhNS4zMiw1LjMyLDAsMSwxLDcuMjctMS45NSw1LjMyLDUuMzIsMCwwLDEtNy4yNywxLjk1IiBzdHlsZT0iZmlsbDogI2ZmZiIvPgo8cGF0aCBkPSJNNjkuMzIsODguODVBNS4zMiw1LjMyLDAsMSwxLDY0LDgzLjUyYTUuMzIsNS4zMiwwLDAsMSw1LjMyLDUuMzIiIHN0eWxlPSJmaWxsOiAjZmZmIi8+CjxnPgo8cGF0aCBkPSJNNjQsNTIuOTRhMTEuMDYsMTEuMDYsMCwwLDEsMi40Ni4yOFYzOS4xNUg2MS41NFY1My4yMkExMS4wNiwxMS4wNiwwLDAsMSw2NCw1Mi45NFoiIHN0eWxlPSJmaWxsOiAjZmZmIi8+CjxwYXRoIGQ9Ik03NC41Nyw2Ny4yNmExMSwxMSwwLDAsMS0yLjQ3LDQuMjVsMTIuMTksNywyLjQ2LTQuMjZaIiBzdHlsZT0iZmlsbDogI2ZmZiIvPgo8cGF0aCBkPSJNNTMuNDMsNjcuMjZsLTEyLjE4LDcsMi40Niw0LjI2LDEyLjE5LTdBMTEsMTEsMCwwLDEsNTMuNDMsNjcuMjZaIiBzdHlsZT0iZmlsbDogI2ZmZiIvPgo8L2c+CjxwYXRoIGQ9Ik03Mi42LDY0QTguNiw4LjYsMCwxLDEsNjQsNTUuNCw4LjYsOC42LDAsMCwxLDcyLjYsNjQiIHN0eWxlPSJmaWxsOiAjZmZmIi8+CjxwYXRoIGQ9Ik0zOS4xLDcwLjU3YTYuNzYsNi43NiwwLDEsMS0yLjQ3LDkuMjMsNi43Niw2Ljc2LDAsMCwxLDIuNDctOS4yMyIgc3R5bGU9ImZpbGw6ICNmZmYiLz4KPHBhdGggZD0iTTgyLjE0LDgyLjI3YTYuNzYsNi43NiwwLDEsMSw5LjIzLTIuNDcsNi43NSw2Ljc1LDAsMCwxLTkuMjMsMi40NyIgc3R5bGU9ImZpbGw6ICNmZmYiLz4KPHBhdGggZD0iTTcwLjc2LDM5LjE1QTYuNzYsNi43NiwwLDEsMSw2NCwzMi4zOWE2Ljc2LDYuNzYsMCwwLDEsNi43Niw2Ljc2IiBzdHlsZT0iZmlsbDogI2ZmZiIvPgo8L2c+Cjwvc3ZnPgo=" ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNode.java index b2d16434d5..a6cbb58cf5 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNode.java @@ -66,7 +66,6 @@ import static org.thingsboard.rule.engine.util.GpsGeofencingEvents.OUTSIDE; "If the presence monitoring strategy \"On each message\" is selected, sends messages via rule node connection type Inside or Outside every time the geofencing condition is satisfied. " + "

    " + "Output connections: Entered, Left, Inside, Outside, Success", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeGpsGeofencingConfig" ) public class TbGpsGeofencingActionNode extends AbstractGeofencingNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java index 89ca1318b7..aa95f8e523 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java @@ -60,7 +60,6 @@ import org.thingsboard.server.common.msg.TbMsg; "

    " + "Available radius units: METER, KILOMETER, FOOT, MILE, NAUTICAL_MILE;

    " + "Output connections: True, False, Failure", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeGpsGeofencingConfig") public class TbGpsGeofencingFilterNode extends AbstractGeofencingNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java index 865eac0aac..5a9e917427 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java @@ -52,7 +52,6 @@ import java.util.Properties; nodeDetails = "Will send record via Kafka producer to Kafka server. " + "Outbound message will contain response fields (offset, partition, topic)" + " from the Kafka in the Message Metadata. For example partition field can be accessed with metadata.partition.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbExternalNodeKafkaConfig", iconUrl = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTUzOCIgaGVpZ2h0PSIyNTAwIiB2aWV3Qm94PSIwIDAgMjU2IDQxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCI+PHBhdGggZD0iTTIwMS44MTYgMjMwLjIxNmMtMTYuMTg2IDAtMzAuNjk3IDcuMTcxLTQwLjYzNCAxOC40NjFsLTI1LjQ2My0xOC4wMjZjMi43MDMtNy40NDIgNC4yNTUtMTUuNDMzIDQuMjU1LTIzLjc5NyAwLTguMjE5LTEuNDk4LTE2LjA3Ni00LjExMi0yMy40MDhsMjUuNDA2LTE3LjgzNWM5LjkzNiAxMS4yMzMgMjQuNDA5IDE4LjM2NSA0MC41NDggMTguMzY1IDI5Ljg3NSAwIDU0LjE4NC0yNC4zMDUgNTQuMTg0LTU0LjE4NCAwLTI5Ljg3OS0yNC4zMDktNTQuMTg0LTU0LjE4NC01NC4xODQtMjkuODc1IDAtNTQuMTg0IDI0LjMwNS01NC4xODQgNTQuMTg0IDAgNS4zNDguODA4IDEwLjUwNSAyLjI1OCAxNS4zODlsLTI1LjQyMyAxNy44NDRjLTEwLjYyLTEzLjE3NS0yNS45MTEtMjIuMzc0LTQzLjMzMy0yNS4xODJ2LTMwLjY0YzI0LjU0NC01LjE1NSA0My4wMzctMjYuOTYyIDQzLjAzNy01My4wMTlDMTI0LjE3MSAyNC4zMDUgOTkuODYyIDAgNjkuOTg3IDAgNDAuMTEyIDAgMTUuODAzIDI0LjMwNSAxNS44MDMgNTQuMTg0YzAgMjUuNzA4IDE4LjAxNCA0Ny4yNDYgNDIuMDY3IDUyLjc2OXYzMS4wMzhDMjUuMDQ0IDE0My43NTMgMCAxNzIuNDAxIDAgMjA2Ljg1NGMwIDM0LjYyMSAyNS4yOTIgNjMuMzc0IDU4LjM1NSA2OC45NHYzMi43NzRjLTI0LjI5OSA1LjM0MS00Mi41NTIgMjcuMDExLTQyLjU1MiA1Mi44OTQgMCAyOS44NzkgMjQuMzA5IDU0LjE4NCA1NC4xODQgNTQuMTg0IDI5Ljg3NSAwIDU0LjE4NC0yNC4zMDUgNTQuMTg0LTU0LjE4NCAwLTI1Ljg4My0xOC4yNTMtNDcuNTUzLTQyLjU1Mi01Mi44OTR2LTMyLjc3NWE2OS45NjUgNjkuOTY1IDAgMCAwIDQyLjYtMjQuNzc2bDI1LjYzMyAxOC4xNDNjLTEuNDIzIDQuODQtMi4yMiA5Ljk0Ni0yLjIyIDE1LjI0IDAgMjkuODc5IDI0LjMwOSA1NC4xODQgNTQuMTg0IDU0LjE4NCAyOS44NzUgMCA1NC4xODQtMjQuMzA1IDU0LjE4NC01NC4xODQgMC0yOS44NzktMjQuMzA5LTU0LjE4NC01NC4xODQtNTQuMTg0em0wLTEyNi42OTVjMTQuNDg3IDAgMjYuMjcgMTEuNzg4IDI2LjI3IDI2LjI3MXMtMTEuNzgzIDI2LjI3LTI2LjI3IDI2LjI3LTI2LjI3LTExLjc4Ny0yNi4yNy0yNi4yN2MwLTE0LjQ4MyAxMS43ODMtMjYuMjcxIDI2LjI3LTI2LjI3MXptLTE1OC4xLTQ5LjMzN2MwLTE0LjQ4MyAxMS43ODQtMjYuMjcgMjYuMjcxLTI2LjI3czI2LjI3IDExLjc4NyAyNi4yNyAyNi4yN2MwIDE0LjQ4My0xMS43ODMgMjYuMjctMjYuMjcgMjYuMjdzLTI2LjI3MS0xMS43ODctMjYuMjcxLTI2LjI3em01Mi41NDEgMzA3LjI3OGMwIDE0LjQ4My0xMS43ODMgMjYuMjctMjYuMjcgMjYuMjdzLTI2LjI3MS0xMS43ODctMjYuMjcxLTI2LjI3YzAtMTQuNDgzIDExLjc4NC0yNi4yNyAyNi4yNzEtMjYuMjdzMjYuMjcgMTEuNzg3IDI2LjI3IDI2LjI3em0tMjYuMjcyLTExNy45N2MtMjAuMjA1IDAtMzYuNjQyLTE2LjQzNC0zNi42NDItMzYuNjM4IDAtMjAuMjA1IDE2LjQzNy0zNi42NDIgMzYuNjQyLTM2LjY0MiAyMC4yMDQgMCAzNi42NDEgMTYuNDM3IDM2LjY0MSAzNi42NDIgMCAyMC4yMDQtMTYuNDM3IDM2LjYzOC0zNi42NDEgMzYuNjM4em0xMzEuODMxIDY3LjE3OWMtMTQuNDg3IDAtMjYuMjctMTEuNzg4LTI2LjI3LTI2LjI3MXMxMS43ODMtMjYuMjcgMjYuMjctMjYuMjcgMjYuMjcgMTEuNzg3IDI2LjI3IDI2LjI3YzAgMTQuNDgzLTExLjc4MyAyNi4yNzEtMjYuMjcgMjYuMjcxeiIvPjwvc3ZnPg==" ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java index 864528b3a4..dd26a64993 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java @@ -42,7 +42,6 @@ import java.util.Map; nodeDescription = "Transforms message to email message", nodeDetails = "Transforms message to email message. If transformation completed successfully output message type will be set to SEND_EMAIL.

    " + "Output connections: Success, Failure.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbTransformationNodeToEmailConfig", icon = "email" ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java index 7be40fc829..27fdce68d9 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java @@ -44,7 +44,6 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback; nodeDetails = "Expects messages with SEND_EMAIL type. Node works only with messages that " + " where created using to Email transformation Node, please connect this Node " + "with to Email Node using Successful chain.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbExternalNodeSendEmailConfig", icon = "send" ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNode.java index d72a23708e..27afa06f90 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNode.java @@ -76,7 +76,6 @@ import static org.thingsboard.rule.engine.math.TbMathArgumentType.CONSTANT; "

    " + "The execution is synchronized in scope of message originator (e.g. device) and server node. " + "If you have rule nodes in different rule chains, they will process messages from the same originator synchronously in the scope of the server node.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeMathFunctionConfig", icon = "calculate" diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java index 75d0e774a2..48c3ba4778 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java @@ -53,7 +53,6 @@ import java.util.Map; "and current value for this key from the incoming message", nodeDetails = "Useful for metering use cases, when you need to calculate consumption based on pulse counter reading.

    " + "Output connections: Success, Other or Failure.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeCalculateDeltaConfig") public class CalculateDeltaNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNode.java index 3362d60fac..6d11419e72 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNode.java @@ -45,7 +45,6 @@ import java.util.concurrent.ExecutionException; "Useful when you need to fetch device credentials and use them for further message processing. " + "For example, use device credentials to interact with external systems.

    " + "Output connections: Success, Failure.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeFetchDeviceCredentialsConfig") public class TbFetchDeviceCredentialsNode extends TbAbstractNodeWithFetchTo { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java index f618befb97..586b5077e2 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java @@ -43,7 +43,6 @@ import org.thingsboard.server.common.msg.TbMsg; "that are not included in the incoming message to use them for further message processing. " + "For example to filter messages based on the threshold value stored in the attributes.

    " + "Output connections: Success, Failure.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeOriginatorAttributesConfig") public class TbGetAttributesNode extends TbAbstractGetAttributesNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java index ea48f3a2c4..37a9d052e6 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java @@ -41,7 +41,6 @@ import org.thingsboard.server.common.data.util.TbPair; "that is stored as customer attributes or telemetry data and used for dynamic message filtering, transformation, " + "or actions such as alarm creation if the threshold is exceeded.

    " + "Output connections: Success, Failure.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeCustomerAttributesConfig") public class TbGetCustomerAttributeNode extends TbAbstractGetEntityDataNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java index eb6eeb1e06..33cc5812e9 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java @@ -50,7 +50,6 @@ import java.util.NoSuchElementException; nodeDetails = "Useful in multi-customer solutions where we need dynamically use customer contact information " + "such as email, phone, address, etc., for notifications via email, SMS, and other notification providers.

    " + "Output connections: Success, Failure.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeEntityDetailsConfig") public class TbGetCustomerDetailsNode extends TbAbstractGetEntityDetailsNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNode.java index 0e257ac3be..1dac58224c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNode.java @@ -42,7 +42,6 @@ import org.thingsboard.server.common.msg.TbMsg; "Useful when you need to retrieve attributes and/or latest telemetry values from device that has a relation " + "to the message originator and use them for further message processing.

    " + "Output connections: Success, Failure.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeDeviceAttributesConfig") public class TbGetDeviceAttrNode extends TbAbstractGetAttributesNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNode.java index 391289cc5d..3807097349 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNode.java @@ -43,7 +43,6 @@ import java.util.concurrent.ExecutionException; nodeDetails = "Fetches fields values specified in the mapping. If specified field is not part of originator fields it will be ignored. " + "Useful when you need to retrieve originator fields and use them for further message processing.

    " + "Output connections: Success, Failure.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeOriginatorFieldsConfig") public class TbGetOriginatorFieldsNode extends TbAbstractGetMappedDataNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java index d107de25c6..639f234f7e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java @@ -42,7 +42,6 @@ import java.util.Arrays; "If multiple related entities are found, only first entity is used for message enrichment, other entities are discarded. " + "Useful when you need to retrieve data from an entity that has a relation to the message originator and use them for further message processing.

    " + "Output connections: Success, Failure.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeRelatedAttributesConfig") public class TbGetRelatedAttributeNode extends TbAbstractGetEntityDataNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java index 8e6e64bb17..b5d9361d50 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java @@ -58,7 +58,6 @@ import java.util.stream.Collectors; "instead of fetching just the latest telemetry or if you need to get the closest telemetry to the fetch interval start or end. " + "Also, this node can be used for telemetry aggregation within configured fetch interval.

    " + "Output connections: Success, Failure.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeGetTelemetryFromDatabase") public class TbGetTelemetryNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java index c1f8e95896..5738eb801b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java @@ -39,7 +39,6 @@ import org.thingsboard.server.common.data.util.TbPair; nodeDetails = "Useful when you need to retrieve some common configuration or threshold set " + "that is stored as tenant attributes or telemetry data and use it for further message processing.

    " + "Output connections: Success, Failure.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeTenantAttributesConfig") public class TbGetTenantAttributeNode extends TbAbstractGetEntityDataNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNode.java index c18cb1f942..ef54aae34e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNode.java @@ -39,7 +39,6 @@ import org.thingsboard.server.common.msg.TbMsg; nodeDetails = "Useful when we need to retrieve contact information from your tenant " + "such as email, phone, address, etc., for notifications via email, SMS, and other notification providers.

    " + "Output connections: Success, Failure.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeEntityDetailsConfig") public class TbGetTenantDetailsNode extends TbAbstractGetEntityDetailsNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java index 56bfe038a8..9576384ab4 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java @@ -57,7 +57,6 @@ import java.util.concurrent.TimeoutException; clusteringMode = ComponentClusteringMode.USER_PREFERENCE, nodeDescription = "Publish messages to the MQTT broker", nodeDetails = "Will publish message payload to the MQTT broker with QoS AT_LEAST_ONCE.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbExternalNodeMqttConfig", icon = "call_split" ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/azure/TbAzureIotHubNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/azure/TbAzureIotHubNode.java index cbf431d63a..836fb215d9 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/azure/TbAzureIotHubNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/azure/TbAzureIotHubNode.java @@ -41,7 +41,6 @@ import org.thingsboard.server.common.data.plugin.ComponentType; clusteringMode = ComponentClusteringMode.SINGLETON, nodeDescription = "Publish messages to the Azure IoT Hub", nodeDetails = "Will publish message payload to the Azure IoT Hub with QoS AT_LEAST_ONCE.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbExternalNodeAzureIotHubConfig" ) public class TbAzureIotHubNode extends TbMqttNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java index 8ae3da7bad..4777a411dc 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java @@ -41,7 +41,6 @@ import java.util.concurrent.ExecutionException; configClazz = TbNotificationNodeConfiguration.class, nodeDescription = "Sends notification to targets using the template", nodeDetails = "Will send notification to the specified targets using the template", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbExternalNodeNotificationConfig", icon = "notifications" ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbSlackNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbSlackNode.java index 78e0b1e46c..a9c62c8d80 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbSlackNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbSlackNode.java @@ -33,7 +33,6 @@ import java.util.concurrent.ExecutionException; configClazz = TbSlackNodeConfiguration.class, nodeDescription = "Send message via Slack", nodeDetails = "Sends message to a Slack channel or user", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbExternalNodeSlackConfig", iconUrl = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTYsMTVBMiwyIDAgMCwxIDQsMTdBMiwyIDAgMCwxIDIsMTVBMiwyIDAgMCwxIDQsMTNINlYxNU03LDE1QTIsMiAwIDAsMSA5LDEzQTIsMiAwIDAsMSAxMSwxNVYyMEEyLDIgMCAwLDEgOSwyMkEyLDIgMCAwLDEgNywyMFYxNU05LDdBMiwyIDAgMCwxIDcsNUEyLDIgMCAwLDEgOSwzQTIsMiAwIDAsMSAxMSw1VjdIOU05LDhBMiwyIDAgMCwxIDExLDEwQTIsMiAwIDAsMSA5LDEySDRBMiwyIDAgMCwxIDIsMTBBMiwyIDAgMCwxIDQsOEg5TTE3LDEwQTIsMiAwIDAsMSAxOSw4QTIsMiAwIDAsMSAyMSwxMEEyLDIgMCAwLDEgMTksMTJIMTdWMTBNMTYsMTBBMiwyIDAgMCwxIDE0LDEyQTIsMiAwIDAsMSAxMiwxMFY1QTIsMiAwIDAsMSAxNCwzQTIsMiAwIDAsMSAxNiw1VjEwTTE0LDE4QTIsMiAwIDAsMSAxNiwyMEEyLDIgMCAwLDEgMTQsMjJBMiwyIDAgMCwxIDEyLDIwVjE4SDE0TTE0LDE3QTIsMiAwIDAsMSAxMiwxNUEyLDIgMCAwLDEgMTQsMTNIMTlBMiwyIDAgMCwxIDIxLDE1QTIsMiAwIDAsMSAxOSwxN0gxNFoiIC8+PC9zdmc+" ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java index c57a9a1433..40fa08ae3c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java @@ -59,8 +59,7 @@ import java.util.concurrent.TimeUnit; nodeDescription = "Process device messages based on device profile settings", nodeDetails = "Create and clear alarms based on alarm rules defined in device profile. The output relation type is either " + "'Alarm Created', 'Alarm Updated', 'Alarm Severity Updated' and 'Alarm Cleared' or simply 'Success' if no alarms were affected.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, - configDirective = "tbDeviceProfileConfig" + configDirective = "tbActionNodeDeviceProfileConfig" ) public class TbDeviceProfileNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java index 8b724ae86e..ebd2a57e59 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java @@ -45,7 +45,6 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback; configClazz = TbRabbitMqNodeConfiguration.class, nodeDescription = "Publish messages to the RabbitMQ", nodeDetails = "Will publish message payload to RabbitMQ queue.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbExternalNodeRabbitMqConfig", iconUrl = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbDpzcGFjZT0icHJlc2VydmUiIHZlcnNpb249IjEuMSIgeT0iMHB4IiB4PSIwcHgiIHZpZXdCb3g9IjAgMCAxMDAwIDEwMDAiPjxwYXRoIHN0cm9rZS13aWR0aD0iLjg0OTU2IiBkPSJtODYwLjQ3IDQxNi4zMmgtMjYyLjAxYy0xMi45MTMgMC0yMy42MTgtMTAuNzA0LTIzLjYxOC0yMy42MTh2LTI3Mi43MWMwLTIwLjMwNS0xNi4yMjctMzYuMjc2LTM2LjI3Ni0zNi4yNzZoLTkzLjc5MmMtMjAuMzA1IDAtMzYuMjc2IDE2LjIyNy0zNi4yNzYgMzYuMjc2djI3MC44NGMtMC4yNTQ4NyAxNC4xMDMtMTEuNDY5IDI1LjU3Mi0yNS43NDIgMjUuNTcybC04NS42MzYgMC42Nzk2NWMtMTQuMTAzIDAtMjUuNTcyLTExLjQ2OS0yNS41NzItMjUuNTcybDAuNjc5NjUtMjcxLjUyYzAtMjAuMzA1LTE2LjIyNy0zNi4yNzYtMzYuMjc2LTM2LjI3NmgtOTMuNTM3Yy0yMC4zMDUgMC0zNi4yNzYgMTYuMjI3LTM2LjI3NiAzNi4yNzZ2NzYzLjg0YzAgMTguMDk2IDE0Ljc4MiAzMi40NTMgMzIuNDUzIDMyLjQ1M2g3MjIuODFjMTguMDk2IDAgMzIuNDUzLTE0Ljc4MiAzMi40NTMtMzIuNDUzdi00MzUuMzFjLTEuMTg5NC0xOC4xODEtMTUuMjkyLTMyLjE5OC0zMy4zODgtMzIuMTk4em0tMTIyLjY4IDI4Ny4wN2MwIDIzLjYxOC0xOC44NiA0Mi40NzgtNDIuNDc4IDQyLjQ3OGgtNzMuOTk3Yy0yMy42MTggMC00Mi40NzgtMTguODYtNDIuNDc4LTQyLjQ3OHYtNzQuMjUyYzAtMjMuNjE4IDE4Ljg2LTQyLjQ3OCA0Mi40NzgtNDIuNDc4aDczLjk5N2MyMy42MTggMCA0Mi40NzggMTguODYgNDIuNDc4IDQyLjQ3OHoiLz48L3N2Zz4=" ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNodeConfiguration.java index 177a4f752b..6049ff919d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNodeConfiguration.java @@ -44,7 +44,6 @@ public class TbRabbitMqNodeConfiguration implements NodeConfigurationstatusCode field can be accessed with metadata.statusCode." + "
    Note- if you use system proxy properties, the next system proxy properties should be added: \"http.proxyHost\" and \"http.proxyPort\" or \"https.proxyHost\" and \"https.proxyPort\" or \"socksProxyHost\" and \"socksProxyPort\"," + "and if your proxy with auth, the next ones should be added: \"tb.proxy.user\" and \"tb.proxy.password\" to the thingsboard.conf file.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbExternalNodeRestApiCallConfig", iconUrl = "data:image/svg+xml;base64,PHN2ZyBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA1MTIgNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbDpzcGFjZT0icHJlc2VydmUiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiB2ZXJzaW9uPSIxLjEiIHk9IjBweCIgeD0iMHB4Ij48ZyB0cmFuc2Zvcm09Im1hdHJpeCguOTQ5NzUgMCAwIC45NDk3NSAxNy4xMiAyNi40OTIpIj48cGF0aCBkPSJtMTY5LjExIDEwOC41NGMtOS45MDY2IDAuMDczNC0xOS4wMTQgNi41NzI0LTIyLjAxNCAxNi40NjlsLTY5Ljk5MyAyMzEuMDhjLTMuNjkwNCAxMi4xODEgMy4yODkyIDI1LjIyIDE1LjQ2OSAyOC45MSAyLjIyNTkgMC42NzQ4MSA0LjQ5NjkgMSA2LjcyODUgMSA5Ljk3MjEgMCAxOS4xNjUtNi41MTUzIDIyLjE4Mi0xNi40NjdhNi41MjI0IDYuNTIyNCAwIDAgMCAwLjAwMiAtMC4wMDJsNjkuOTktMjMxLjA3YTYuNTIyNCA2LjUyMjQgMCAwIDAgMCAtMC4wMDJjMy42ODU1LTEyLjE4MS0zLjI4Ny0yNS4yMjUtMTUuNDcxLTI4LjkxMi0yLjI4MjUtMC42OTE0NS00LjYxMTYtMS4wMTY5LTYuODk4NC0xem04NC45ODggMGMtOS45MDQ4IDAuMDczNC0xOS4wMTggNi41Njc1LTIyLjAxOCAxNi40NjlsLTY5Ljk4NiAyMzEuMDhjLTMuNjg5OCAxMi4xNzkgMy4yODUzIDI1LjIxNyAxNS40NjUgMjguOTA4IDIuMjI5NyAwLjY3NjQ3IDQuNTAwOCAxLjAwMiA2LjczMjQgMS4wMDIgOS45NzIxIDAgMTkuMTY1LTYuNTE1MyAyMi4xODItMTYuNDY3YTYuNTIyNCA2LjUyMjQgMCAwIDAgMC4wMDIgLTAuMDAybDY5Ljk4OC0yMzEuMDdjMy42OTA4LTEyLjE4MS0zLjI4NTItMjUuMjIzLTE1LjQ2Ny0yOC45MTItMi4yODE0LTAuNjkyMzEtNC42MTA4LTEuMDE4OS02Ljg5ODQtMS4wMDJ6bS0yMTcuMjkgNDIuMjNjLTEyLjcyOS0wLjAwMDg3LTIzLjE4OCAxMC40NTYtMjMuMTg4IDIzLjE4NiAwLjAwMSAxMi43MjggMTAuNDU5IDIzLjE4NiAyMy4xODggMjMuMTg2IDEyLjcyNy0wLjAwMSAyMy4xODMtMTAuNDU5IDIzLjE4NC0yMy4xODYgMC4wMDA4NzYtMTIuNzI4LTEwLjQ1Ni0yMy4xODUtMjMuMTg0LTIzLjE4NnptMCAxNDYuNjRjLTEyLjcyNy0wLjAwMDg3LTIzLjE4NiAxMC40NTUtMjMuMTg4IDIzLjE4NC0wLjAwMDg3MyAxMi43MjkgMTAuNDU4IDIzLjE4OCAyMy4xODggMjMuMTg4IDEyLjcyOC0wLjAwMSAyMy4xODQtMTAuNDYgMjMuMTg0LTIzLjE4OC0wLjAwMS0xMi43MjYtMTAuNDU3LTIzLjE4My0yMy4xODQtMjMuMTg0em0yNzAuNzkgNDIuMjExYy0xMi43MjcgMC0yMy4xODQgMTAuNDU3LTIzLjE4NCAyMy4xODRzMTAuNDU1IDIzLjE4OCAyMy4xODQgMjMuMTg4aDE1NC45OGMxMi43MjkgMCAyMy4xODYtMTAuNDYgMjMuMTg2LTIzLjE4OCAwLjAwMS0xMi43MjgtMTAuNDU4LTIzLjE4NC0yMy4xODYtMjMuMTg0eiIgdHJhbnNmb3JtPSJtYXRyaXgoMS4wMzc2IDAgMCAxLjAzNzYgLTcuNTY3NiAtMTQuOTI1KSIgc3Ryb2tlLXdpZHRoPSIxLjI2OTMiLz48L2c+PC9zdmc+" ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbSendRestApiCallReplyNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbSendRestApiCallReplyNode.java index 84441fb213..0c6e449576 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbSendRestApiCallReplyNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbSendRestApiCallReplyNode.java @@ -35,7 +35,6 @@ import java.util.UUID; configClazz = TbSendRestApiCallReplyNodeConfiguration.class, nodeDescription = "Sends reply to REST API call to rule engine", nodeDetails = "Expects messages with any message type. Forwards incoming message as a reply to REST API call sent to rule engine.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeSendRestApiCallReplyConfig", icon = "call_merge" ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNode.java index c343b242bf..1f071b590e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNode.java @@ -48,7 +48,6 @@ import java.util.UUID; configClazz = TbSendRpcReplyNodeConfiguration.class, nodeDescription = "Sends reply to RPC call from device", nodeDetails = "Expects messages with any message type. Will forward message body to the device.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeRpcReplyConfig", icon = "call_merge" ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java index 81863f9410..76d6649734 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java @@ -49,7 +49,6 @@ import java.util.concurrent.TimeUnit; nodeDescription = "Sends RPC call to device", nodeDetails = "Expects messages with \"method\" and \"params\". Will forward response from device to next nodes." + "If the RPC call request is originated by REST API call from user, will forward the response to user immediately.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeRpcRequestConfig", icon = "call_made" ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNode.java index 0024936b89..3fbd074a14 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNode.java @@ -35,7 +35,6 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback; configClazz = TbSendSmsNodeConfiguration.class, nodeDescription = "Sends SMS message via SMS provider.", nodeDetails = "Will send SMS message by populating target phone numbers and sms message fields using values derived from message metadata.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbExternalNodeSendSmsConfig", icon = "sms" ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java index e83a125265..c57c62e2ae 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java @@ -62,7 +62,6 @@ import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_R "Additionally if checkbox Send attributes updated notification is set to true, rule node will put the \"Attributes Updated\" " + "event for SHARED_SCOPE and SERVER_SCOPE attributes updates to the corresponding rule engine queue." + "Performance checkbox 'Save attributes only if the value changes' will skip attributes overwrites for values with no changes (avoid concurrent writes because this check is not transactional; will not update 'Last updated time' for skipped attributes).", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeAttributesConfig", icon = "file_upload" ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNode.java index f1f1a29f3c..a89a4f37d8 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNode.java @@ -45,7 +45,6 @@ import static org.thingsboard.server.common.data.DataConstants.SCOPE; " a key selected in the configuration, it will be ignored. If delete operation is completed successfully, " + " rule node will send the \"Attributes Deleted\" event to the root chain of the message originator and " + " send the incoming message via Success chain, otherwise, Failure chain is used.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeDeleteAttributesConfig", icon = "remove_circle" ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java index 27f45feb47..1b3acb6dae 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java @@ -15,8 +15,11 @@ */ package org.thingsboard.rule.engine.telemetry; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.gson.JsonParser; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; @@ -24,6 +27,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TimeseriesSaveRequest; import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.rule.engine.telemetry.strategy.ProcessingStrategy; import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TenantProfile; @@ -32,13 +36,20 @@ import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; +import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.UUID; import java.util.concurrent.TimeUnit; +import static org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNodeConfiguration.ProcessingSettings; +import static org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNodeConfiguration.ProcessingSettings.Advanced; +import static org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNodeConfiguration.ProcessingSettings.Deduplicate; +import static org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNodeConfiguration.ProcessingSettings.OnEveryMessage; +import static org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNodeConfiguration.ProcessingSettings.WebSocketsOnly; import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @Slf4j @@ -46,21 +57,52 @@ import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_RE type = ComponentType.ACTION, name = "save time series", configClazz = TbMsgTimeseriesNodeConfiguration.class, - nodeDescription = "Saves time series data", - nodeDetails = "Saves time series telemetry data based on configurable TTL parameter. Expects messages with 'POST_TELEMETRY_REQUEST' message type. " + - "Timestamp in milliseconds will be taken from metadata.ts, otherwise 'now' message timestamp will be applied. " + - "Allows stopping updating values for incoming keys in the latest ts_kv table if 'skipLatestPersistence' is set to true.\n " + - "
    " + - "Enable 'useServerTs' param to use the timestamp of the message processing instead of the timestamp from the message. " + - "Useful for all sorts of sequential processing if you merge messages from multiple sources (devices, assets, etc).\n" + - "
    " + - "In the case of sequential processing, the platform guarantees that the messages are processed in the order of their submission to the queue. " + - "However, the timestamp of the messages originated by multiple devices/servers may be unsynchronized long before they are pushed to the queue. " + - "The DB layer has certain optimizations to ignore the updates of the \"attributes\" and \"latest values\" tables if the new record has a timestamp that is older than the previous record. " + - "So, to make sure that all the messages will be processed correctly, one should enable this parameter for sequential message processing scenarios.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, + nodeDescription = """ + Saves time series data with a configurable TTL and according to configured processing strategies. + """, + nodeDetails = """ + Node performs three actions: +
      +
    • Time series: save time series data to a ts_kv table in a DB.
    • +
    • Latest values: save time series data to a ts_kv_latest table in a DB.
    • +
    • WebSockets: notify WebSockets subscriptions about time series data updates.
    • +
    + + For each action, three processing strategies are available: +
      +
    • On every message: perform the action for every message.
    • +
    • Deduplicate: perform the action only for the first message from a particular originator within a configurable interval.
    • +
    • Skip: never perform the action.
    • +
    + + Processing strategies are configured using processing settings, which support two modes: +
      +
    • Basic +
        +
      • On every message: applies the "On every message" strategy to all actions.
      • +
      • Deduplicate: applies the "Deduplicate" strategy (with a specified interval) to all actions.
      • +
      • WebSockets only: applies the "Skip" strategy to Time series and Latest values, and the "On every message" strategy to WebSockets.
      • +
      +
    • +
    • Advanced: configure each action’s strategy independently.
    • +
    + + By default, the timestamp is taken from metadata.ts. You can enable + Use server timestamp to always use the current server time instead. This is particularly + useful in sequential processing scenarios where messages may arrive with out-of-order timestamps from + multiple sources. Note that the DB layer may ignore older records for attributes and latest values, + so enabling Use server timestamp can ensure correct ordering. +

    + The TTL is taken first from metadata.TTL. If absent, the node configuration’s default + TTL is used. If neither is set, the tenant profile default applies. +

    + This node expects messages of type POST_TELEMETRY_REQUEST. +

    + Output connections: Success, Failure. + """, configDirective = "tbActionNodeTimeseriesConfig", - icon = "file_upload" + icon = "file_upload", + version = 1 ) public class TbMsgTimeseriesNode implements TbNode { @@ -68,15 +110,18 @@ public class TbMsgTimeseriesNode implements TbNode { private TbContext ctx; private long tenantProfileDefaultStorageTtl; + private ProcessingSettings processingSettings; + @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config = TbNodeUtils.convert(configuration, TbMsgTimeseriesNodeConfiguration.class); this.ctx = ctx; ctx.addTenantProfileListener(this::onTenantProfileUpdate); onTenantProfileUpdate(ctx.getTenantProfile()); + processingSettings = config.getProcessingSettings(); } - void onTenantProfileUpdate(TenantProfile tenantProfile) { + private void onTenantProfileUpdate(TenantProfile tenantProfile) { DefaultTenantProfileConfiguration configuration = (DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration(); tenantProfileDefaultStorageTtl = TimeUnit.DAYS.toSeconds(configuration.getDefaultStorageTtlDays()); } @@ -88,6 +133,15 @@ public class TbMsgTimeseriesNode implements TbNode { return; } long ts = computeTs(msg, config.isUseServerTs()); + + TimeseriesSaveRequest.Strategy strategy = determineSaveStrategy(ts, msg.getOriginator().getId()); + + // short-circuit + if (!strategy.saveTimeseries() && !strategy.saveLatest() && !strategy.sendWsUpdate()) { + ctx.tellSuccess(msg); + return; + } + String src = msg.getData(); Map> tsKvMap = JsonConverter.convertToTelemetry(JsonParser.parseString(src), ts); if (tsKvMap.isEmpty()) { @@ -111,7 +165,7 @@ public class TbMsgTimeseriesNode implements TbNode { .entityId(msg.getOriginator()) .entries(tsKvEntryList) .ttl(ttl) - .saveLatest(!config.isSkipLatestPersistence()) + .strategy(strategy) .callback(new TelemetryNodeCallback(ctx, msg)) .build()); } @@ -120,9 +174,56 @@ public class TbMsgTimeseriesNode implements TbNode { return ignoreMetadataTs ? System.currentTimeMillis() : msg.getMetaDataTs(); } + private TimeseriesSaveRequest.Strategy determineSaveStrategy(long ts, UUID originatorUuid) { + if (processingSettings instanceof OnEveryMessage) { + return TimeseriesSaveRequest.Strategy.SAVE_ALL; + } + if (processingSettings instanceof WebSocketsOnly) { + return TimeseriesSaveRequest.Strategy.WS_ONLY; + } + if (processingSettings instanceof Deduplicate deduplicate) { + boolean isFirstMsgInInterval = deduplicate.getProcessingStrategy().shouldProcess(ts, originatorUuid); + return isFirstMsgInInterval ? TimeseriesSaveRequest.Strategy.SAVE_ALL : TimeseriesSaveRequest.Strategy.SKIP_ALL; + } + if (processingSettings instanceof Advanced advanced) { + return new TimeseriesSaveRequest.Strategy( + advanced.timeseries().shouldProcess(ts, originatorUuid), + advanced.latest().shouldProcess(ts, originatorUuid), + advanced.webSockets().shouldProcess(ts, originatorUuid) + ); + } + // should not happen + throw new IllegalArgumentException("Unknown processing settings type: " + processingSettings.getClass().getSimpleName()); + } + @Override public void destroy() { ctx.removeListeners(); } + @Override + public TbPair upgrade(int fromVersion, JsonNode oldConfiguration) throws TbNodeException { + boolean hasChanges = false; + switch (fromVersion) { + case 0: + hasChanges = true; + JsonNode skipLatestPersistence = oldConfiguration.get("skipLatestPersistence"); + if (skipLatestPersistence != null && "true".equals(skipLatestPersistence.asText())) { + var skipLatestProcessingSettings = new Advanced( + ProcessingStrategy.onEveryMessage(), + ProcessingStrategy.skip(), + ProcessingStrategy.onEveryMessage() + ); + ((ObjectNode) oldConfiguration).set("processingSettings", JacksonUtil.valueToTree(skipLatestProcessingSettings)); + } else { + ((ObjectNode) oldConfiguration).set("processingSettings", JacksonUtil.valueToTree(new OnEveryMessage())); + } + ((ObjectNode) oldConfiguration).remove("skipLatestPersistence"); + break; + default: + break; + } + return new TbPair<>(hasChanges, oldConfiguration); + } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNodeConfiguration.java index 1c33778a6b..88524e3751 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNodeConfiguration.java @@ -15,22 +15,84 @@ */ package org.thingsboard.rule.engine.telemetry; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import jakarta.validation.constraints.NotNull; import lombok.Data; +import lombok.Getter; import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.rule.engine.telemetry.strategy.ProcessingStrategy; + +import java.util.Objects; + +import static org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNodeConfiguration.ProcessingSettings.Advanced; +import static org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNodeConfiguration.ProcessingSettings.Deduplicate; +import static org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNodeConfiguration.ProcessingSettings.OnEveryMessage; +import static org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNodeConfiguration.ProcessingSettings.WebSocketsOnly; @Data public class TbMsgTimeseriesNodeConfiguration implements NodeConfiguration { private long defaultTTL; - private boolean skipLatestPersistence; private boolean useServerTs; + @NotNull + private TbMsgTimeseriesNodeConfiguration.ProcessingSettings processingSettings; @Override public TbMsgTimeseriesNodeConfiguration defaultConfiguration() { TbMsgTimeseriesNodeConfiguration configuration = new TbMsgTimeseriesNodeConfiguration(); configuration.setDefaultTTL(0L); - configuration.setSkipLatestPersistence(false); configuration.setUseServerTs(false); + configuration.setProcessingSettings(new OnEveryMessage()); return configuration; } + + @JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = "type" + ) + @JsonSubTypes({ + @JsonSubTypes.Type(value = OnEveryMessage.class, name = "ON_EVERY_MESSAGE"), + @JsonSubTypes.Type(value = WebSocketsOnly.class, name = "WEBSOCKETS_ONLY"), + @JsonSubTypes.Type(value = Deduplicate.class, name = "DEDUPLICATE"), + @JsonSubTypes.Type(value = Advanced.class, name = "ADVANCED") + }) + sealed interface ProcessingSettings permits OnEveryMessage, Deduplicate, WebSocketsOnly, Advanced { + + record OnEveryMessage() implements ProcessingSettings {} + + record WebSocketsOnly() implements ProcessingSettings {} + + @Getter + final class Deduplicate implements ProcessingSettings { + + private final int deduplicationIntervalSecs; + + @JsonIgnore + private final ProcessingStrategy processingStrategy; + + @JsonCreator + Deduplicate(@JsonProperty("deduplicationIntervalSecs") int deduplicationIntervalSecs) { + this.deduplicationIntervalSecs = deduplicationIntervalSecs; + processingStrategy = ProcessingStrategy.deduplicate(deduplicationIntervalSecs); + } + + } + + record Advanced(ProcessingStrategy timeseries, ProcessingStrategy latest, ProcessingStrategy webSockets) implements ProcessingSettings { + + public Advanced { + Objects.requireNonNull(timeseries); + Objects.requireNonNull(latest); + Objects.requireNonNull(webSockets); + } + + } + + } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/strategy/DeduplicateProcessingStrategy.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/strategy/DeduplicateProcessingStrategy.java new file mode 100644 index 0000000000..a9c6fc38fd --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/strategy/DeduplicateProcessingStrategy.java @@ -0,0 +1,89 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.telemetry.strategy; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.LoadingCache; +import com.google.common.collect.Sets; +import com.google.common.primitives.Longs; + +import java.time.Duration; +import java.util.Set; +import java.util.UUID; + +final class DeduplicateProcessingStrategy implements ProcessingStrategy { + + private static final int MIN_DEDUPLICATION_INTERVAL_SECS = 1; + private static final int MAX_DEDUPLICATION_INTERVAL_SECS = (int) Duration.ofDays(1L).toSeconds(); + + private static final long MIN_INTERVAL_EXPIRY_MILLIS = Duration.ofMinutes(10L).toMillis(); + private static final int INTERVAL_EXPIRY_FACTOR = 10; + private static final long MAX_INTERVAL_EXPIRY_MILLIS = Duration.ofDays(2L).toMillis(); + + private static final int MAX_TOTAL_INTERVALS_DURATION_SECS = (int) Duration.ofDays(2L).toSeconds(); + private static final int MAX_NUMBER_OF_INTERVALS = 100; + + private final long deduplicationIntervalMillis; + private final LoadingCache> deduplicationCache; + + @JsonCreator + public DeduplicateProcessingStrategy(@JsonProperty("deduplicationIntervalSecs") int deduplicationIntervalSecs) { + if (deduplicationIntervalSecs < MIN_DEDUPLICATION_INTERVAL_SECS || deduplicationIntervalSecs > MAX_DEDUPLICATION_INTERVAL_SECS) { + throw new IllegalArgumentException("Deduplication interval must be at least " + MIN_DEDUPLICATION_INTERVAL_SECS + " second(s) " + + "and at most " + MAX_DEDUPLICATION_INTERVAL_SECS + " second(s), was " + deduplicationIntervalSecs + " second(s)"); + } + deduplicationIntervalMillis = Duration.ofSeconds(deduplicationIntervalSecs).toMillis(); + deduplicationCache = Caffeine.newBuilder() + .softValues() + .expireAfterAccess(calculateExpireAfterAccess(deduplicationIntervalSecs)) + .maximumSize(calculateMaxNumberOfDeduplicationIntervals(deduplicationIntervalSecs)) + .build(__ -> Sets.newConcurrentHashSet()); + } + + /** + * Calculates the expire-after-access duration. By default, we keep each deduplication interval + * alive for 10 “iterations” (interval duration × 10). However, we never let this drop below + * 10 minutes to ensure adequate retention for small intervals, nor exceed 48 hours to prevent + * storing stale data in memory. + */ + private static Duration calculateExpireAfterAccess(int deduplicationIntervalSecs) { + long desiredExpiryMillis = Duration.ofSeconds(deduplicationIntervalSecs).toMillis() * INTERVAL_EXPIRY_FACTOR; + return Duration.ofMillis(Longs.constrainToRange(desiredExpiryMillis, MIN_INTERVAL_EXPIRY_MILLIS, MAX_INTERVAL_EXPIRY_MILLIS)); + } + + /** + * Calculates the maximum number of deduplication intervals we will store in the cache. + * We limit retention to two days to avoid stale data and cap it at 100 intervals to manage memory usage. + */ + private static long calculateMaxNumberOfDeduplicationIntervals(int deduplicationIntervalSecs) { + int numberOfDeduplicationIntervals = MAX_TOTAL_INTERVALS_DURATION_SECS / deduplicationIntervalSecs; + return Math.min(numberOfDeduplicationIntervals, MAX_NUMBER_OF_INTERVALS); + } + + @JsonProperty("deduplicationIntervalSecs") + public long getDeduplicationIntervalSecs() { + return Duration.ofMillis(deduplicationIntervalMillis).toSeconds(); + } + + @Override + public boolean shouldProcess(long ts, UUID originatorUuid) { + long intervalNumber = ts / deduplicationIntervalMillis; + return deduplicationCache.get(intervalNumber).add(originatorUuid); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/relation/RelationMsgConstructorFactory.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/strategy/OnEveryMessageProcessingStrategy.java similarity index 51% rename from application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/relation/RelationMsgConstructorFactory.java rename to rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/strategy/OnEveryMessageProcessingStrategy.java index dd1642c6a0..9e8a87364a 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/relation/RelationMsgConstructorFactory.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/strategy/OnEveryMessageProcessingStrategy.java @@ -13,14 +13,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.edge.rpc.constructor.relation; +package org.thingsboard.rule.engine.telemetry.strategy; -import org.springframework.stereotype.Component; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.constructor.BaseMsgConstructorFactory; +import com.fasterxml.jackson.annotation.JsonCreator; -@Component -@TbCoreComponent -public class RelationMsgConstructorFactory extends BaseMsgConstructorFactory { +import java.util.UUID; + +final class OnEveryMessageProcessingStrategy implements ProcessingStrategy { + + private static final OnEveryMessageProcessingStrategy INSTANCE = new OnEveryMessageProcessingStrategy(); + + private OnEveryMessageProcessingStrategy() {} + + @JsonCreator + public static OnEveryMessageProcessingStrategy getInstance() { + return INSTANCE; + } + + @Override + public boolean shouldProcess(long ts, UUID originatorUuid) { + return true; + } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/strategy/ProcessingStrategy.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/strategy/ProcessingStrategy.java new file mode 100644 index 0000000000..724727dc22 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/strategy/ProcessingStrategy.java @@ -0,0 +1,49 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.telemetry.strategy; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +import java.util.UUID; + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = "type" +) +@JsonSubTypes({ + @JsonSubTypes.Type(value = OnEveryMessageProcessingStrategy.class, name = "ON_EVERY_MESSAGE"), + @JsonSubTypes.Type(value = DeduplicateProcessingStrategy.class, name = "DEDUPLICATE"), + @JsonSubTypes.Type(value = SkipProcessingStrategy.class, name = "SKIP") +}) +public sealed interface ProcessingStrategy permits OnEveryMessageProcessingStrategy, DeduplicateProcessingStrategy, SkipProcessingStrategy { + + static ProcessingStrategy onEveryMessage() { + return OnEveryMessageProcessingStrategy.getInstance(); + } + + static ProcessingStrategy deduplicate(int deduplicationIntervalSecs) { + return new DeduplicateProcessingStrategy(deduplicationIntervalSecs); + } + + static ProcessingStrategy skip() { + return SkipProcessingStrategy.getInstance(); + } + + boolean shouldProcess(long ts, UUID originatorUuid); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/resource/ResourceMsgConstructorFactory.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/strategy/SkipProcessingStrategy.java similarity index 53% rename from application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/resource/ResourceMsgConstructorFactory.java rename to rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/strategy/SkipProcessingStrategy.java index e2e3970b41..66b966653d 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/resource/ResourceMsgConstructorFactory.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/strategy/SkipProcessingStrategy.java @@ -13,14 +13,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.edge.rpc.constructor.resource; +package org.thingsboard.rule.engine.telemetry.strategy; -import org.springframework.stereotype.Component; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.constructor.BaseMsgConstructorFactory; +import com.fasterxml.jackson.annotation.JsonCreator; -@Component -@TbCoreComponent -public class ResourceMsgConstructorFactory extends BaseMsgConstructorFactory { +import java.util.UUID; + +final class SkipProcessingStrategy implements ProcessingStrategy { + + private static final SkipProcessingStrategy INSTANCE = new SkipProcessingStrategy(); + + private SkipProcessingStrategy() {} + + @JsonCreator + public static SkipProcessingStrategy getInstance() { + return INSTANCE; + } + + @Override + public boolean shouldProcess(long ts, UUID originatorUuid) { + return false; + } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transaction/TbSynchronizationBeginNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transaction/TbSynchronizationBeginNode.java index 543767cfeb..50d668b1d4 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transaction/TbSynchronizationBeginNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transaction/TbSynchronizationBeginNode.java @@ -34,7 +34,6 @@ import org.thingsboard.server.common.msg.TbMsg; nodeDetails = "This node should be used together with \"synchronization end\" node. \n This node will put messages into queue based on message originator id. \n" + "Subsequent messages will not be processed until the previous message processing is completed or timeout event occurs.\n" + "Size of the queue per originator and timeout values are configurable on a system level", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbNodeEmptyConfig") @Deprecated public class TbSynchronizationBeginNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transaction/TbSynchronizationEndNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transaction/TbSynchronizationEndNode.java index 6f1344acf3..9407414dcb 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transaction/TbSynchronizationEndNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transaction/TbSynchronizationEndNode.java @@ -32,7 +32,6 @@ import org.thingsboard.server.common.msg.TbMsg; configClazz = EmptyNodeConfiguration.class, nodeDescription = "This Node is now deprecated. Use \"Checkpoint\" instead.", nodeDetails = "", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = ("tbNodeEmptyConfig") ) @Deprecated diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java index d590bbbb75..e45f1dd8b2 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java @@ -54,7 +54,6 @@ import static org.thingsboard.rule.engine.transform.OriginatorSource.RELATED; "
  • Entity by name pattern - specify entity type and name pattern of new originator. Following entity types are supported: " + "'Device', 'Asset', 'Entity View', 'Edge' or 'User'.
  • " + "Output connections: Success, Failure.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbTransformationNodeChangeOriginatorConfig", icon = "find_replace" ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNode.java index 1417618a54..aadc3a3851 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNode.java @@ -46,7 +46,6 @@ import java.util.stream.Collectors; nodeDetails = "Copies key-value pairs from the message to message metadata, or vice-versa, according to the configured direction and keys. " + "Regular expressions can be used to define which keys-value pairs to copy. Any configured key not found in the source will be ignored.

    " + "Output connections: Success, Failure.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbTransformationNodeCopyKeysConfig", icon = "content_copy" ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNode.java index cf31381af2..412b523786 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNode.java @@ -46,7 +46,6 @@ import java.util.stream.Collectors; nodeDetails = "Deletes key-value pairs from the message or message metadata according to the configured " + "keys and/or regular expressions.

    " + "Output connections: Success, Failure.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbTransformationNodeDeleteKeysConfig", icon = "remove_circle" ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbJsonPathNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbJsonPathNode.java index 043e914c2c..af9121e8a1 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbJsonPathNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbJsonPathNode.java @@ -40,7 +40,6 @@ import java.util.concurrent.ExecutionException; nodeDescription = "Transforms incoming message body using JSONPath expression.", nodeDetails = "JSONPath expression specifies a path to an element or a set of elements in a JSON structure.

    " + "Output connections: Success, Failure.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, icon = "functions", configDirective = "tbTransformationNodeJsonPathConfig" ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNode.java index 490504c42b..904e624c75 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNode.java @@ -43,7 +43,6 @@ import java.util.concurrent.ExecutionException; nodeDetails = "Renames keys in the message or message metadata according to the provided mapping. " + "If key to rename doesn't exist in the specified source (message or message metadata) it will be ignored.

    " + "Output connections: Success, Failure.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbTransformationNodeRenameKeysConfig", icon = "find_replace" ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java index 92a3dd8d09..588d073cac 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java @@ -43,7 +43,6 @@ import java.util.concurrent.ExecutionException; nodeDetails = "Splits an array message into individual elements, with each element sent as a separate message. " + "All outbound messages will have the same type and metadata as the original array message.

    " + "Output connections: Success, Failure.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, icon = "content_copy", configDirective = "tbNodeEmptyConfig" ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java index 3227c0c52c..01c6bd36c2 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java @@ -41,7 +41,6 @@ import java.util.List; "{ msg: new payload,
       metadata: new metadata,
       msgType: new msgType }

    " + "All fields in resulting object are optional and will be taken from original message if not specified.

    " + "Output connections: Success, Failure.", - uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbTransformationNodeScriptConfig" ) public class TbTransformMsgNode extends TbAbstractTransformNode { diff --git a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js deleted file mode 100644 index 2ad9ac666b..0000000000 --- a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js +++ /dev/null @@ -1 +0,0 @@ -System.register(["@angular/core","@shared/public-api","@ngrx/store","@angular/forms","@core/public-api","@ngx-translate/core","@angular/cdk/keycodes","@angular/common","@home/components/public-api","tslib","rxjs","@angular/cdk/coercion","rxjs/operators"],(function(e){"use strict";var t,n,r,a,i,o,l,s,p,m,d,u,c,f,g,h,y,b,v,x,C,S,T,I,E,F,q,A,k,N,w,M,B,V,O,D,L,P,R,_,j,G,K,U,H,z,$,Q,J,Y,W,X,Z,ee,te,ne,re,ae,ie;return{setters:[function(e){t=e,n=e.EventEmitter,r=e.forwardRef,a=e.ɵNG_COMP_DEF},function(e){i=e.RuleNodeConfigurationComponent,o=e.AttributeScope,l=e.telemetryTypeTranslations,s=e.ScriptLanguage,p=e.AlarmSeverity,m=e.alarmSeverityTranslations,d=e.EntitySearchDirection,u=e.EntityType,c=e.entityFields,f=e.messageTypeNames,g=e.MessageType,h=e.coerceBoolean,y=e.PageComponent,b=e.entitySearchDirectionTranslations,v=e,x=e.AlarmStatus,C=e.alarmStatusTranslations,S=e.SharedModule,T=e.AggregationType,I=e.aggregationTranslations,E=e.NotificationType,F=e.SlackChanelType,q=e.SlackChanelTypesTranslateMap},function(e){A=e},function(e){k=e,N=e.Validators,w=e.FormArray,M=e.FormGroup,B=e.NgControl,V=e.NG_VALUE_ACCESSOR,O=e.NG_VALIDATORS},function(e){D=e.getCurrentAuthState,L=e,P=e.isDefinedAndNotNull,R=e.isEqual,_=e.deepTrim,j=e.isObject,G=e.isNotEmptyStr},function(e){K=e},function(e){U=e.ENTER,H=e.COMMA,z=e.SEMICOLON},function(e){$=e.CommonModule},function(e){Q=e.HomeComponentsModule},function(e){J=e.__decorate},function(e){Y=e.Subject,W=e.takeUntil,X=e.of},function(e){Z=e.coerceBooleanProperty},function(e){ee=e.startWith,te=e.map,ne=e.mergeMap,re=e.share,ae=e.tap,ie=e.takeUntil}],execute:function(){class oe extends i{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.emptyConfigForm}onConfigurationSet(e){this.emptyConfigForm=this.fb.group({})}static{this.ɵfac=function(e){return new(e||oe)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:oe,selectors:[["tb-node-empty-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:1,vars:0,template:function(e,n){1&e&&t.ɵɵelement(0,"div")},dependencies:t.ɵɵgetComponentDepsFactory(oe),encapsulation:2})}}function le(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.customer-name-pattern-required")," "))}e("EmptyConfigComponent",oe);class se extends i{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.assignCustomerConfigForm}onConfigurationSet(e){this.assignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[N.required,N.pattern(/.*\S.*/)]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]]})}prepareOutputConfig(e){return e.customerNamePattern=e.customerNamePattern.trim(),e}static{this.ɵfac=function(e){return new(e||se)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:se,selectors:[["tb-action-node-assign-to-customer-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:13,vars:5,consts:[[1,"flex","flex-col",3,"formGroup"],[1,"tb-form-panel","no-padding","no-border"],["subscriptSizing","dynamic",1,"mat-block"],["translate",""],["required","","matInput","","formControlName","customerNamePattern"],[4,"ngIf"],[1,"tb-form-row"],["formControlName","createCustomerIfNotExists",1,"mat-slide"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"div",1)(2,"mat-form-field",2)(3,"mat-label",3),t.ɵɵtext(4,"tb.rulenode.customer-name-pattern"),t.ɵɵelementEnd(),t.ɵɵelement(5,"input",4),t.ɵɵtemplate(6,le,3,3,"mat-error",5),t.ɵɵelementStart(7,"mat-hint",3),t.ɵɵtext(8,"tb.rulenode.customer-name-pattern-hint"),t.ɵɵelementEnd()(),t.ɵɵelementStart(9,"div",6)(10,"mat-slide-toggle",7),t.ɵɵtext(11),t.ɵɵpipe(12,"translate"),t.ɵɵelementEnd()()()()),2&e&&(t.ɵɵproperty("formGroup",n.assignCustomerConfigForm),t.ɵɵadvance(6),t.ɵɵproperty("ngIf",n.assignCustomerConfigForm.get("customerNamePattern").hasError("required")||n.assignCustomerConfigForm.get("customerNamePattern").hasError("pattern")),t.ɵɵadvance(5),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(12,3,"tb.rulenode.create-customer-if-not-exists")," "))},dependencies:t.ɵɵgetComponentDepsFactory(se),encapsulation:2})}}e("AssignCustomerConfigComponent",se);const pe=()=>({standalone:!0});function me(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",15),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=n.$implicit,r=t.ɵɵnextContext();t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,2,r.telemetryTypeTranslationsMap.get(e))," ")}}function de(e,n){1&e&&(t.ɵɵelementStart(0,"div",12),t.ɵɵpipe(1,"translate"),t.ɵɵelementStart(2,"mat-slide-toggle",16),t.ɵɵtext(3),t.ɵɵpipe(4,"translate"),t.ɵɵelementEnd()()),2&e&&(t.ɵɵpropertyInterpolate("tb-hint-tooltip-icon",t.ɵɵpipeBind1(1,2,"tb.rulenode.send-attributes-updated-notification-hint")),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(4,4,"tb.rulenode.send-attributes-updated-notification")," "))}function ue(e,n){1&e&&(t.ɵɵelementStart(0,"div",12),t.ɵɵpipe(1,"translate"),t.ɵɵelementStart(2,"mat-slide-toggle",17),t.ɵɵtext(3),t.ɵɵpipe(4,"translate"),t.ɵɵelementEnd()()),2&e&&(t.ɵɵpropertyInterpolate("tb-hint-tooltip-icon",t.ɵɵpipeBind1(1,2,"tb.rulenode.notify-device-on-update-hint")),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(4,4,"tb.rulenode.notify-device")," "))}class ce extends i{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopeMap=o,this.attributeScopes=Object.keys(o),this.telemetryTypeTranslationsMap=l}configForm(){return this.attributesConfigForm}onConfigurationSet(e){this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[N.required]],notifyDevice:[!e||e.notifyDevice,[]],sendAttributesUpdatedNotification:[!!e&&e.sendAttributesUpdatedNotification,[]],updateAttributesOnlyOnValueChange:[!!e&&e.updateAttributesOnlyOnValueChange,[]]}),this.attributesConfigForm.get("scope").valueChanges.subscribe((e=>{e!==o.SHARED_SCOPE&&this.attributesConfigForm.get("notifyDevice").patchValue(!1,{emitEvent:!1}),e===o.CLIENT_SCOPE&&this.attributesConfigForm.get("sendAttributesUpdatedNotification").patchValue(!1,{emitEvent:!1}),this.attributesConfigForm.get("updateAttributesOnlyOnValueChange").patchValue(!1,{emitEvent:!1})}))}static{this.ɵfac=function(e){return new(e||ce)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:ce,selectors:[["tb-action-node-attributes-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:31,vars:24,consts:[[1,"tb-form-panel","no-border","no-padding",3,"formGroup"],[1,"tb-form-panel","stroked"],[3,"hintText"],[1,"tb-form-row","no-border","no-padding","tb-standard-fields"],[1,"flex"],["required","","matInput","","formControlName","scope",1,"tb-entity-type-select"],[3,"value",4,"ngFor","ngForOf"],["type","text","matInput","","readonly","","disabled","",3,"ngModel","ngModelOptions"],["type","button","matSuffix","","mat-icon-button","","aria-label","Copy","ngxClipboard","",3,"cbContent","matTooltip"],["aria-hidden","false","aria-label","help-icon"],[1,"tb-settings"],["translate",""],[1,"tb-form-row","no-border","no-padding",3,"tb-hint-tooltip-icon"],["formControlName","updateAttributesOnlyOnValueChange",1,"mat-slide"],["class","tb-form-row no-border no-padding",3,"tb-hint-tooltip-icon",4,"ngIf"],[3,"value"],["formControlName","sendAttributesUpdatedNotification",1,"mat-slide"],["formControlName","notifyDevice",1,"mat-slide"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"div",1),t.ɵɵelement(2,"tb-example-hint",2),t.ɵɵelementStart(3,"div",3)(4,"mat-form-field",4)(5,"mat-label"),t.ɵɵtext(6),t.ɵɵpipe(7,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(8,"mat-select",5),t.ɵɵtemplate(9,me,3,4,"mat-option",6),t.ɵɵelementEnd()(),t.ɵɵelementStart(10,"mat-form-field",4)(11,"mat-label"),t.ɵɵtext(12),t.ɵɵpipe(13,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(14,"input",7),t.ɵɵelementStart(15,"button",8),t.ɵɵpipe(16,"translate"),t.ɵɵelementStart(17,"mat-icon",9),t.ɵɵtext(18,"content_copy "),t.ɵɵelementEnd()()()()(),t.ɵɵelementStart(19,"section",1)(20,"mat-expansion-panel",10)(21,"mat-expansion-panel-header")(22,"mat-panel-title",11),t.ɵɵtext(23,"tb.rulenode.advanced-settings"),t.ɵɵelementEnd()(),t.ɵɵelementStart(24,"div",12),t.ɵɵpipe(25,"translate"),t.ɵɵelementStart(26,"mat-slide-toggle",13),t.ɵɵtext(27),t.ɵɵpipe(28,"translate"),t.ɵɵelementEnd()(),t.ɵɵtemplate(29,de,5,6,"div",14)(30,ue,5,6,"div",14),t.ɵɵelementEnd()()()),2&e&&(t.ɵɵproperty("formGroup",n.attributesConfigForm),t.ɵɵadvance(2),t.ɵɵproperty("hintText","tb.rulenode.attributes-scope-hint"),t.ɵɵadvance(4),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(7,13,"tb.rulenode.attributes-scope")),t.ɵɵadvance(3),t.ɵɵproperty("ngForOf",n.attributeScopes),t.ɵɵadvance(3),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(13,15,"tb.rulenode.attributes-scope-value")),t.ɵɵadvance(2),t.ɵɵproperty("ngModel",n.attributesConfigForm.get("scope").value)("ngModelOptions",t.ɵɵpureFunction0(23,pe)),t.ɵɵadvance(),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(16,17,"tb.rulenode.attributes-scope-value-copy")),t.ɵɵproperty("cbContent",n.attributesConfigForm.get("scope").value),t.ɵɵadvance(9),t.ɵɵpropertyInterpolate("tb-hint-tooltip-icon",t.ɵɵpipeBind1(25,19,n.attributesConfigForm.get("updateAttributesOnlyOnValueChange").value?"tb.rulenode.update-attributes-only-on-value-change-hint-enabled":"tb.rulenode.update-attributes-only-on-value-change-hint")),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(28,21,"tb.rulenode.update-attributes-only-on-value-change")," "),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",n.attributesConfigForm.get("scope").value!==n.attributeScopeMap.CLIENT_SCOPE),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.attributesConfigForm.get("scope").value===n.attributeScopeMap.SHARED_SCOPE))},dependencies:t.ɵɵgetComponentDepsFactory(ce),encapsulation:2})}}e("AttributesConfigComponent",ce);const fe=["jsFuncComponent"],ge=["tbelFuncComponent"],he=()=>["msg","metadata","msgType"];function ye(e,n){1&e&&t.ɵɵelement(0,"tb-script-lang",12)}function be(e,n){if(1&e){const e=t.ɵɵgetCurrentView();t.ɵɵelementStart(0,"tb-js-func",13,0)(2,"button",14),t.ɵɵpipe(3,"translate"),t.ɵɵlistener("click",(function(){t.ɵɵrestoreView(e);const n=t.ɵɵnextContext();return t.ɵɵresetView(n.testScript())})),t.ɵɵelementStart(4,"mat-icon",15),t.ɵɵtext(5,"bug_report"),t.ɵɵelementEnd()()()}if(2&e){const e=t.ɵɵnextContext();t.ɵɵproperty("functionArgs",t.ɵɵpureFunction0(4,he)),t.ɵɵadvance(2),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(3,2,e.testScriptLabel))}}function ve(e,n){if(1&e){const e=t.ɵɵgetCurrentView();t.ɵɵelementStart(0,"tb-js-func",16,1)(2,"button",14),t.ɵɵpipe(3,"translate"),t.ɵɵlistener("click",(function(){t.ɵɵrestoreView(e);const n=t.ɵɵnextContext();return t.ɵɵresetView(n.testScript())})),t.ɵɵelementStart(4,"mat-icon",15),t.ɵɵtext(5,"bug_report"),t.ɵɵelementEnd()()()}if(2&e){const e=t.ɵɵnextContext();t.ɵɵproperty("functionArgs",t.ɵɵpureFunction0(6,he))("disableUndefinedCheck",!0)("scriptLanguage",e.scriptLanguage.TBEL),t.ɵɵadvance(2),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(3,4,e.testScriptLabel))}}function xe(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.alarm-type-required")," "))}class Ce extends i{constructor(e,t,r,a){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=r,this.translate=a,this.tbelEnabled=D(this.store).tbelEnabled,this.scriptLanguage=s,this.changeScript=new n,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-details-function"}configForm(){return this.clearAlarmConfigForm}onConfigurationSet(e){this.clearAlarmConfigForm=this.fb.group({scriptLang:[e?e.scriptLang:s.JS,[N.required]],alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[]],alarmDetailsBuildTbel:[e?e.alarmDetailsBuildTbel:null,[]],alarmType:[e?e.alarmType:null,[N.required]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.clearAlarmConfigForm.get("scriptLang").value;t!==s.TBEL||this.tbelEnabled||(t=s.JS,this.clearAlarmConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.clearAlarmConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValidators(t===s.JS?[N.required]:[]),this.clearAlarmConfigForm.get("alarmDetailsBuildJs").updateValueAndValidity({emitEvent:e}),this.clearAlarmConfigForm.get("alarmDetailsBuildTbel").setValidators(t===s.TBEL?[N.required]:[]),this.clearAlarmConfigForm.get("alarmDetailsBuildTbel").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=s.JS)),e}testScript(e){const t=this.clearAlarmConfigForm.get("scriptLang").value,n=t===s.JS?"alarmDetailsBuildJs":"alarmDetailsBuildTbel",r=t===s.JS?"rulenode/clear_alarm_node_script_fn":"rulenode/tbel/clear_alarm_node_script_fn",a=this.clearAlarmConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(a,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.clearAlarmConfigForm.get(n).setValue(e),this.changeScript.emit())}))}onValidate(){this.clearAlarmConfigForm.get("scriptLang").value===s.JS&&this.jsFuncComponent.validateOnSubmit()}static{this.ɵfac=function(e){return new(e||Ce)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder),t.ɵɵdirectiveInject(L.NodeScriptTestService),t.ɵɵdirectiveInject(K.TranslateService))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Ce,selectors:[["tb-action-node-clear-alarm-config"]],viewQuery:function(e,n){if(1&e&&(t.ɵɵviewQuery(fe,5),t.ɵɵviewQuery(ge,5)),2&e){let e;t.ɵɵqueryRefresh(e=t.ɵɵloadQuery())&&(n.jsFuncComponent=e.first),t.ɵɵqueryRefresh(e=t.ɵɵloadQuery())&&(n.tbelFuncComponent=e.first)}},features:[t.ɵɵInheritDefinitionFeature],decls:15,vars:8,consts:[["jsFuncComponent",""],["tbelFuncComponent",""],[1,"flex","flex-col",3,"formGroup"],["formControlName","scriptLang",4,"ngIf"],["formControlName","alarmDetailsBuildJs","functionName","Details","helpId","rulenode/clear_alarm_node_script_fn","noValidate","true",3,"functionArgs",4,"ngIf"],["formControlName","alarmDetailsBuildTbel","functionName","Details","helpId","rulenode/tbel/clear_alarm_node_script_fn","noValidate","true",3,"functionArgs","disableUndefinedCheck","scriptLanguage",4,"ngIf"],[1,"flex","flex-row",2,"padding-bottom","16px"],["mat-button","","mat-raised-button","","color","primary",3,"click"],["subscriptSizing","dynamic",1,"mat-block"],["translate",""],["required","","matInput","","formControlName","alarmType"],[4,"ngIf"],["formControlName","scriptLang"],["formControlName","alarmDetailsBuildJs","functionName","Details","helpId","rulenode/clear_alarm_node_script_fn","noValidate","true",3,"functionArgs"],["toolbarSuffixButton","","mat-icon-button","","matTooltipPosition","above",1,"tb-mat-32",3,"click","matTooltip"],["color","primary",1,"material-icons"],["formControlName","alarmDetailsBuildTbel","functionName","Details","helpId","rulenode/tbel/clear_alarm_node_script_fn","noValidate","true",3,"functionArgs","disableUndefinedCheck","scriptLanguage"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",2),t.ɵɵtemplate(1,ye,1,0,"tb-script-lang",3)(2,be,6,5,"tb-js-func",4)(3,ve,6,7,"tb-js-func",5),t.ɵɵelementStart(4,"div",6)(5,"button",7),t.ɵɵlistener("click",(function(){return n.testScript()})),t.ɵɵtext(6),t.ɵɵpipe(7,"translate"),t.ɵɵelementEnd()(),t.ɵɵelementStart(8,"mat-form-field",8)(9,"mat-label",9),t.ɵɵtext(10,"tb.rulenode.alarm-type"),t.ɵɵelementEnd(),t.ɵɵelement(11,"input",10),t.ɵɵtemplate(12,xe,3,3,"mat-error",11),t.ɵɵelementStart(13,"mat-hint",9),t.ɵɵtext(14,"tb.rulenode.general-pattern-hint"),t.ɵɵelementEnd()()()),2&e&&(t.ɵɵproperty("formGroup",n.clearAlarmConfigForm),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.tbelEnabled),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.clearAlarmConfigForm.get("scriptLang").value===n.scriptLanguage.JS),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.clearAlarmConfigForm.get("scriptLang").value===n.scriptLanguage.TBEL),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(7,6,n.testScriptLabel)," "),t.ɵɵadvance(6),t.ɵɵproperty("ngIf",n.clearAlarmConfigForm.get("alarmType").hasError("required")))},dependencies:t.ɵɵgetComponentDepsFactory(Ce),encapsulation:2})}}e("ClearAlarmConfigComponent",Ce);const Se=["jsFuncComponent"],Te=["tbelFuncComponent"],Ie=()=>["msg","metadata","msgType"];function Ee(e,n){1&e&&(t.ɵɵelementStart(0,"mat-checkbox",7),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.overwrite-alarm-details")," "))}function Fe(e,n){1&e&&t.ɵɵelement(0,"tb-script-lang",14)}function qe(e,n){if(1&e){const e=t.ɵɵgetCurrentView();t.ɵɵelementStart(0,"tb-js-func",15,0)(2,"button",16),t.ɵɵpipe(3,"translate"),t.ɵɵlistener("click",(function(){t.ɵɵrestoreView(e);const n=t.ɵɵnextContext(2);return t.ɵɵresetView(n.testScript())})),t.ɵɵelementStart(4,"mat-icon",17),t.ɵɵtext(5,"bug_report"),t.ɵɵelementEnd()()()}if(2&e){const e=t.ɵɵnextContext(2);t.ɵɵproperty("functionArgs",t.ɵɵpureFunction0(4,Ie)),t.ɵɵadvance(2),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(3,2,e.testScriptLabel))}}function Ae(e,n){if(1&e){const e=t.ɵɵgetCurrentView();t.ɵɵelementStart(0,"tb-js-func",18,1)(2,"button",16),t.ɵɵpipe(3,"translate"),t.ɵɵlistener("click",(function(){t.ɵɵrestoreView(e);const n=t.ɵɵnextContext(2);return t.ɵɵresetView(n.testScript())})),t.ɵɵelementStart(4,"mat-icon",17),t.ɵɵtext(5,"bug_report"),t.ɵɵelementEnd()()()}if(2&e){const e=t.ɵɵnextContext(2);t.ɵɵproperty("functionArgs",t.ɵɵpureFunction0(6,Ie))("disableUndefinedCheck",!0)("scriptLanguage",e.scriptLanguage.TBEL),t.ɵɵadvance(2),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(3,4,e.testScriptLabel))}}function ke(e,n){if(1&e){const e=t.ɵɵgetCurrentView();t.ɵɵelementStart(0,"section",8),t.ɵɵtemplate(1,Fe,1,0,"tb-script-lang",9)(2,qe,6,5,"tb-js-func",10)(3,Ae,6,7,"tb-js-func",11),t.ɵɵelementStart(4,"div",12)(5,"button",13),t.ɵɵlistener("click",(function(){t.ɵɵrestoreView(e);const n=t.ɵɵnextContext();return t.ɵɵresetView(n.testScript())})),t.ɵɵtext(6),t.ɵɵpipe(7,"translate"),t.ɵɵelementEnd()()()}if(2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(),t.ɵɵproperty("ngIf",e.tbelEnabled),t.ɵɵadvance(),t.ɵɵproperty("ngIf",e.createAlarmConfigForm.get("scriptLang").value===e.scriptLanguage.JS),t.ɵɵadvance(),t.ɵɵproperty("ngIf",e.createAlarmConfigForm.get("scriptLang").value===e.scriptLanguage.TBEL),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(7,4,e.testScriptLabel)," ")}}function Ne(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.alarm-type-required")," "))}function we(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",32),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=n.$implicit,r=t.ɵɵnextContext(3);t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,2,r.alarmSeverityTranslationMap.get(e))," ")}}function Me(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.alarm-severity-required")," "))}function Be(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-form-field",29)(1,"mat-label",20),t.ɵɵtext(2,"tb.rulenode.alarm-severity"),t.ɵɵelementEnd(),t.ɵɵelementStart(3,"mat-select",30),t.ɵɵtemplate(4,we,3,4,"mat-option",31),t.ɵɵelementEnd(),t.ɵɵtemplate(5,Me,3,3,"mat-error",22),t.ɵɵelementEnd()),2&e){const e=t.ɵɵnextContext(2);t.ɵɵadvance(4),t.ɵɵproperty("ngForOf",e.alarmSeverities),t.ɵɵadvance(),t.ɵɵproperty("ngIf",e.createAlarmConfigForm.get("severity").hasError("required"))}}function Ve(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.alarm-severity-required")," "))}function Oe(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-form-field",19)(1,"mat-label",20),t.ɵɵtext(2,"tb.rulenode.alarm-severity-pattern"),t.ɵɵelementEnd(),t.ɵɵelement(3,"input",33),t.ɵɵtemplate(4,Ve,3,3,"mat-error",22),t.ɵɵelement(5,"mat-hint",34),t.ɵɵpipe(6,"translate"),t.ɵɵpipe(7,"safe"),t.ɵɵelementEnd()),2&e){const e=t.ɵɵnextContext(2);t.ɵɵadvance(4),t.ɵɵproperty("ngIf",e.createAlarmConfigForm.get("severity").hasError("required")),t.ɵɵadvance(),t.ɵɵproperty("innerHTML",t.ɵɵpipeBind2(7,4,t.ɵɵpipeBind1(6,2,"tb.rulenode.alarm-severity-pattern-hint"),"html"),t.ɵɵsanitizeHtml)}}function De(e,n){if(1&e){const e=t.ɵɵgetCurrentView();t.ɵɵelementStart(0,"mat-chip-row",38),t.ɵɵlistener("removed",(function(){const n=t.ɵɵrestoreView(e).$implicit,r=t.ɵɵnextContext(3);return t.ɵɵresetView(r.removeKey(n,"relationTypes"))})),t.ɵɵtext(1),t.ɵɵelementStart(2,"mat-icon",39),t.ɵɵtext(3,"close"),t.ɵɵelementEnd()()}if(2&e){const e=n.$implicit;t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",e," ")}}function Le(e,n){if(1&e){const e=t.ɵɵgetCurrentView();t.ɵɵelementStart(0,"section")(1,"mat-form-field",35)(2,"mat-label",20),t.ɵɵtext(3,"tb.rulenode.relation-types-list"),t.ɵɵelementEnd(),t.ɵɵelementStart(4,"mat-chip-grid",null,2),t.ɵɵtemplate(6,De,4,1,"mat-chip-row",36),t.ɵɵelementStart(7,"input",37),t.ɵɵpipe(8,"translate"),t.ɵɵlistener("matChipInputTokenEnd",(function(n){t.ɵɵrestoreView(e);const r=t.ɵɵnextContext(2);return t.ɵɵresetView(r.addKey(n,"relationTypes"))})),t.ɵɵelementEnd()(),t.ɵɵelementStart(9,"mat-hint",20),t.ɵɵtext(10,"tb.rulenode.relation-types-list-hint"),t.ɵɵelementEnd()()()}if(2&e){const e=t.ɵɵreference(5),n=t.ɵɵnextContext(2);t.ɵɵadvance(6),t.ɵɵproperty("ngForOf",n.createAlarmConfigForm.get("relationTypes").value),t.ɵɵadvance(),t.ɵɵpropertyInterpolate("placeholder",t.ɵɵpipeBind1(8,5,"tb.rulenode.relation-types-list")),t.ɵɵproperty("matChipInputFor",e)("matChipInputSeparatorKeyCodes",n.separatorKeysCodes)("matChipInputAddOnBlur",!0)}}function Pe(e,n){if(1&e&&(t.ɵɵelementStart(0,"section",8)(1,"mat-form-field",19)(2,"mat-label",20),t.ɵɵtext(3,"tb.rulenode.alarm-type"),t.ɵɵelementEnd(),t.ɵɵelement(4,"input",21),t.ɵɵtemplate(5,Ne,3,3,"mat-error",22),t.ɵɵelementStart(6,"mat-hint",20),t.ɵɵtext(7,"tb.rulenode.general-pattern-hint"),t.ɵɵelementEnd()(),t.ɵɵelementStart(8,"mat-checkbox",23),t.ɵɵtext(9),t.ɵɵpipe(10,"translate"),t.ɵɵelementEnd(),t.ɵɵtemplate(11,Be,6,2,"mat-form-field",24)(12,Oe,8,7,"mat-form-field",25),t.ɵɵelementStart(13,"mat-checkbox",26),t.ɵɵtext(14),t.ɵɵpipe(15,"translate"),t.ɵɵelementEnd(),t.ɵɵtemplate(16,Le,11,7,"section",22),t.ɵɵelementStart(17,"mat-checkbox",27),t.ɵɵtext(18),t.ɵɵpipe(19,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(20,"mat-checkbox",28),t.ɵɵtext(21),t.ɵɵpipe(22,"translate"),t.ɵɵelementEnd()()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(5),t.ɵɵproperty("ngIf",e.createAlarmConfigForm.get("alarmType").hasError("required")),t.ɵɵadvance(4),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(10,8,"tb.rulenode.use-alarm-severity-pattern")," "),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",!e.createAlarmConfigForm.get("dynamicSeverity").value),t.ɵɵadvance(),t.ɵɵproperty("ngIf",e.createAlarmConfigForm.get("dynamicSeverity").value),t.ɵɵadvance(2),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(15,10,"tb.rulenode.propagate")," "),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",!0===e.createAlarmConfigForm.get("propagate").value),t.ɵɵadvance(2),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(19,12,"tb.rulenode.propagate-to-owner")," "),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(22,14,"tb.rulenode.propagate-to-tenant")," ")}}class Re extends i{constructor(e,t,r,a){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=r,this.translate=a,this.alarmSeverities=Object.keys(p),this.alarmSeverityTranslationMap=m,this.separatorKeysCodes=[U,H,z],this.tbelEnabled=D(this.store).tbelEnabled,this.scriptLanguage=s,this.changeScript=new n,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-details-function"}configForm(){return this.createAlarmConfigForm}onConfigurationSet(e){this.createAlarmConfigForm=this.fb.group({scriptLang:[e?e.scriptLang:s.JS,[N.required]],alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[]],alarmDetailsBuildTbel:[e?e.alarmDetailsBuildTbel:null,[]],useMessageAlarmData:[!!e&&e.useMessageAlarmData,[]],overwriteAlarmDetails:[!!e&&e.overwriteAlarmDetails,[]],alarmType:[e?e.alarmType:null,[]],severity:[e?e.severity:null,[]],propagate:[!!e&&e.propagate,[]],relationTypes:[e?e.relationTypes:null,[]],propagateToOwner:[!!e&&e.propagateToOwner,[]],propagateToTenant:[!!e&&e.propagateToTenant,[]],dynamicSeverity:!1}),this.createAlarmConfigForm.get("dynamicSeverity").valueChanges.subscribe((e=>{e?this.createAlarmConfigForm.get("severity").patchValue("",{emitEvent:!1}):this.createAlarmConfigForm.get("severity").patchValue(this.alarmSeverities[0],{emitEvent:!1})}))}validatorTriggers(){return["useMessageAlarmData","overwriteAlarmDetails","scriptLang"]}updateValidators(e){const t=this.createAlarmConfigForm.get("useMessageAlarmData").value,n=this.createAlarmConfigForm.get("overwriteAlarmDetails").value;t?(this.createAlarmConfigForm.get("alarmType").setValidators([]),this.createAlarmConfigForm.get("severity").setValidators([])):(this.createAlarmConfigForm.get("alarmType").setValidators([N.required]),this.createAlarmConfigForm.get("severity").setValidators([N.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e});let r=this.createAlarmConfigForm.get("scriptLang").value;r!==s.TBEL||this.tbelEnabled||(r=s.JS,this.createAlarmConfigForm.get("scriptLang").patchValue(r,{emitEvent:!1}),setTimeout((()=>{this.createAlarmConfigForm.updateValueAndValidity({emitEvent:!0})})));const a=!1===t||!0===n;this.createAlarmConfigForm.get("alarmDetailsBuildJs").setValidators(a&&r===s.JS?[N.required]:[]),this.createAlarmConfigForm.get("alarmDetailsBuildTbel").setValidators(a&&r===s.TBEL?[N.required]:[]),this.createAlarmConfigForm.get("alarmDetailsBuildJs").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("alarmDetailsBuildTbel").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=s.JS)),e}testScript(e){const t=this.createAlarmConfigForm.get("scriptLang").value,n=t===s.JS?"alarmDetailsBuildJs":"alarmDetailsBuildTbel",r=t===s.JS?"rulenode/create_alarm_node_script_fn":"rulenode/tbel/create_alarm_node_script_fn",a=this.createAlarmConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(a,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.createAlarmConfigForm.get(n).setValue(e),this.changeScript.emit())}))}removeKey(e,t){const n=this.createAlarmConfigForm.get(t).value,r=n.indexOf(e);r>=0&&(n.splice(r,1),this.createAlarmConfigForm.get(t).setValue(n,{emitEvent:!0}))}addKey(e,t){const n=e.input;let r=e.value;if((r||"").trim()){r=r.trim();let e=this.createAlarmConfigForm.get(t).value;e&&-1!==e.indexOf(r)||(e||(e=[]),e.push(r),this.createAlarmConfigForm.get(t).setValue(e,{emitEvent:!0}))}n&&(n.value="")}onValidate(){const e=this.createAlarmConfigForm.get("useMessageAlarmData").value,t=this.createAlarmConfigForm.get("overwriteAlarmDetails").value;if(!e||t){this.createAlarmConfigForm.get("scriptLang").value===s.JS&&this.jsFuncComponent.validateOnSubmit()}}static{this.ɵfac=function(e){return new(e||Re)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder),t.ɵɵdirectiveInject(L.NodeScriptTestService),t.ɵɵdirectiveInject(K.TranslateService))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Re,selectors:[["tb-action-node-create-alarm-config"]],viewQuery:function(e,n){if(1&e&&(t.ɵɵviewQuery(Se,5),t.ɵɵviewQuery(Te,5)),2&e){let e;t.ɵɵqueryRefresh(e=t.ɵɵloadQuery())&&(n.jsFuncComponent=e.first),t.ɵɵqueryRefresh(e=t.ɵɵloadQuery())&&(n.tbelFuncComponent=e.first)}},features:[t.ɵɵInheritDefinitionFeature],decls:7,vars:7,consts:[["jsFuncComponent",""],["tbelFuncComponent",""],["relationTypesChipList",""],[1,"flex","flex-col",3,"formGroup"],["formControlName","useMessageAlarmData"],["formControlName","overwriteAlarmDetails",4,"ngIf"],["class","flex flex-col",4,"ngIf"],["formControlName","overwriteAlarmDetails"],[1,"flex","flex-col"],["formControlName","scriptLang",4,"ngIf"],["formControlName","alarmDetailsBuildJs","functionName","Details","helpId","rulenode/create_alarm_node_script_fn","noValidate","true",3,"functionArgs",4,"ngIf"],["formControlName","alarmDetailsBuildTbel","functionName","Details","helpId","rulenode/tbel/create_alarm_node_script_fn","noValidate","true",3,"functionArgs","disableUndefinedCheck","scriptLanguage",4,"ngIf"],[1,"flex","flex-row",2,"padding-bottom","16px"],["mat-button","","mat-raised-button","","color","primary",3,"click"],["formControlName","scriptLang"],["formControlName","alarmDetailsBuildJs","functionName","Details","helpId","rulenode/create_alarm_node_script_fn","noValidate","true",3,"functionArgs"],["toolbarSuffixButton","","mat-icon-button","","matTooltipPosition","above",1,"tb-mat-32",3,"click","matTooltip"],["color","primary",1,"material-icons"],["formControlName","alarmDetailsBuildTbel","functionName","Details","helpId","rulenode/tbel/create_alarm_node_script_fn","noValidate","true",3,"functionArgs","disableUndefinedCheck","scriptLanguage"],["subscriptSizing","dynamic",1,"flex-1"],["translate",""],["required","","matInput","","formControlName","alarmType"],[4,"ngIf"],["formControlName","dynamicSeverity"],["class","flex-1",4,"ngIf"],["class","flex-1","subscriptSizing","dynamic",4,"ngIf"],["formControlName","propagate"],["formControlName","propagateToOwner"],["formControlName","propagateToTenant"],[1,"flex-1"],["formControlName","severity","required",""],[3,"value",4,"ngFor","ngForOf"],[3,"value"],["matInput","","formControlName","severity","required",""],[3,"innerHTML"],["floatLabel","always","subscriptSizing","dynamic",1,"mat-block"],[3,"removed",4,"ngFor","ngForOf"],["matInput","","type","text",3,"matChipInputTokenEnd","placeholder","matChipInputFor","matChipInputSeparatorKeyCodes","matChipInputAddOnBlur"],[3,"removed"],["matChipRemove",""]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",3)(1,"mat-checkbox",4),t.ɵɵtext(2),t.ɵɵpipe(3,"translate"),t.ɵɵelementEnd(),t.ɵɵtemplate(4,Ee,3,3,"mat-checkbox",5)(5,ke,8,6,"section",6)(6,Pe,23,16,"section",6),t.ɵɵelementEnd()),2&e&&(t.ɵɵproperty("formGroup",n.createAlarmConfigForm),t.ɵɵadvance(2),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(3,5,"tb.rulenode.use-message-alarm-data")," "),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",!0===n.createAlarmConfigForm.get("useMessageAlarmData").value),t.ɵɵadvance(),t.ɵɵproperty("ngIf",!1===n.createAlarmConfigForm.get("useMessageAlarmData").value||!0===n.createAlarmConfigForm.get("overwriteAlarmDetails").value),t.ɵɵadvance(),t.ɵɵproperty("ngIf",!1===n.createAlarmConfigForm.get("useMessageAlarmData").value))},dependencies:t.ɵɵgetComponentDepsFactory(Re),encapsulation:2})}}function _e(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",21),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=n.$implicit,r=t.ɵɵnextContext();t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,2,r.directionTypeTranslations.get(e))," ")}}function je(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-form-field",22)(1,"mat-label"),t.ɵɵtext(2),t.ɵɵpipe(3,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(4,"input",23),t.ɵɵelementEnd()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(2),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(3,1,e.entityTypeNamePatternTranslation.get(e.createRelationConfigForm.get("entityType").value)))}}function Ge(e,n){1&e&&(t.ɵɵelementStart(0,"mat-form-field",22)(1,"mat-label",5),t.ɵɵtext(2,"tb.rulenode.profile-name"),t.ɵɵelementEnd(),t.ɵɵelement(3,"input",24),t.ɵɵelementEnd())}function Ke(e,n){1&e&&t.ɵɵelement(0,"tb-example-hint",25),2&e&&t.ɵɵproperty("hintText","tb.rulenode.kv-map-pattern-hint")}function Ue(e,n){1&e&&(t.ɵɵelementStart(0,"div",26),t.ɵɵpipe(1,"translate"),t.ɵɵelementStart(2,"mat-slide-toggle",27),t.ɵɵtext(3),t.ɵɵpipe(4,"translate"),t.ɵɵelementEnd()()),2&e&&(t.ɵɵpropertyInterpolate("tb-hint-tooltip-icon",t.ɵɵpipeBind1(1,2,"tb.rulenode.create-entity-if-not-exists-hint")),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(4,4,"tb.rulenode.create-entity-if-not-exists")," "))}e("CreateAlarmConfigComponent",Re);class He extends i{constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(d),this.directionTypeTranslations=new Map([[d.FROM,"tb.rulenode.search-direction-from"],[d.TO,"tb.rulenode.search-direction-to"]]),this.entityType=u,this.entityTypeNamePatternTranslation=new Map([[u.DEVICE,"tb.rulenode.device-name-pattern"],[u.ASSET,"tb.rulenode.asset-name-pattern"],[u.ENTITY_VIEW,"tb.rulenode.entity-view-name-pattern"],[u.CUSTOMER,"tb.rulenode.customer-title-pattern"],[u.USER,"tb.rulenode.user-name-pattern"],[u.DASHBOARD,"tb.rulenode.dashboard-name-pattern"],[u.EDGE,"tb.rulenode.edge-name-pattern"]]),this.allowedEntityTypes=[u.DEVICE,u.ASSET,u.ENTITY_VIEW,u.TENANT,u.CUSTOMER,u.USER,u.DASHBOARD,u.EDGE]}configForm(){return this.createRelationConfigForm}onConfigurationSet(e){this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[N.required]],entityType:[e?e.entityType:null,[N.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[N.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]]})}validatorTriggers(){return["entityType","createEntityIfNotExists"]}updateValidators(e){const t=this.createRelationConfigForm.get("entityType").value;if(t?this.createRelationConfigForm.get("entityNamePattern").setValidators([N.required,N.pattern(/.*\S.*/)]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==u.DEVICE&&t!==u.ASSET)this.createRelationConfigForm.get("entityTypePattern").setValidators([]);else{const e=[N.pattern(/.*\S.*/)];this.createRelationConfigForm.get("createEntityIfNotExists").value&&e.push(N.required),this.createRelationConfigForm.get("entityTypePattern").setValidators(e)}this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})}prepareOutputConfig(e){return e.entityNamePattern=e.entityNamePattern?e.entityNamePattern.trim():null,e.entityTypePattern=e.entityTypePattern?e.entityTypePattern.trim():null,e}static{this.ɵfac=function(e){return new(e||He)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:He,selectors:[["tb-action-node-create-relation-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:36,vars:19,consts:[[1,"tb-form-panel","no-padding","no-border",3,"formGroup"],[1,"tb-form-panel","stroked","no-padding-bottom"],["translate","",1,"tb-form-panel-title"],[1,"flex","flex-col"],["hideRequiredMarker","",1,"mat-block"],["translate",""],["required","","matInput","","formControlName","direction"],[3,"value",4,"ngFor","ngForOf"],["required","","formControlName","relationType"],[1,"flex","flex-row","gap-4"],["showLabel","","required","","formControlName","entityType",1,"flex-1",3,"allowedEntityTypes"],["class","mat-block flex-1",4,"ngIf"],[3,"hintText",4,"ngIf"],["style","margin-bottom: 18px","class","tb-form-row no-border no-padding",3,"tb-hint-tooltip-icon",4,"ngIf"],[1,"tb-form-panel","stroked","no-padding"],[1,"tb-settings"],[2,"padding","16px"],[1,"tb-form-panel","no-border","no-padding-top"],[1,"tb-form-row","no-border","no-padding",3,"tb-hint-tooltip-icon"],["formControlName","removeCurrentRelations",1,"mat-slide"],["formControlName","changeOriginatorToRelatedEntity",1,"mat-slide"],[3,"value"],[1,"mat-block","flex-1"],["required","","matInput","","formControlName","entityNamePattern"],["matInput","","formControlName","entityTypePattern"],[3,"hintText"],[1,"tb-form-row","no-border","no-padding",2,"margin-bottom","18px",3,"tb-hint-tooltip-icon"],["formControlName","createEntityIfNotExists",1,"mat-slide"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"div",1)(2,"div",2),t.ɵɵtext(3,"tb.rulenode.relation-parameters"),t.ɵɵelementEnd(),t.ɵɵelementStart(4,"div",3)(5,"mat-form-field",4)(6,"mat-label",5),t.ɵɵtext(7,"relation.direction"),t.ɵɵelementEnd(),t.ɵɵelementStart(8,"mat-select",6),t.ɵɵtemplate(9,_e,3,4,"mat-option",7),t.ɵɵelementEnd()(),t.ɵɵelement(10,"tb-relation-type-autocomplete",8),t.ɵɵelementEnd()(),t.ɵɵelementStart(11,"div",1)(12,"div",2),t.ɵɵtext(13,"tb.rulenode.target-entity"),t.ɵɵelementEnd(),t.ɵɵelementStart(14,"div",9),t.ɵɵelement(15,"tb-entity-type-select",10),t.ɵɵtemplate(16,je,5,3,"mat-form-field",11)(17,Ge,4,0,"mat-form-field",11),t.ɵɵelementEnd(),t.ɵɵtemplate(18,Ke,1,1,"tb-example-hint",12)(19,Ue,5,6,"div",13),t.ɵɵelementEnd(),t.ɵɵelementStart(20,"section",14)(21,"mat-expansion-panel",15)(22,"mat-expansion-panel-header",16)(23,"mat-panel-title",5),t.ɵɵtext(24,"tb.rulenode.advanced-settings"),t.ɵɵelementEnd()(),t.ɵɵelementStart(25,"div",17)(26,"div",18),t.ɵɵpipe(27,"translate"),t.ɵɵelementStart(28,"mat-slide-toggle",19),t.ɵɵtext(29),t.ɵɵpipe(30,"translate"),t.ɵɵelementEnd()(),t.ɵɵelementStart(31,"div",18),t.ɵɵpipe(32,"translate"),t.ɵɵelementStart(33,"mat-slide-toggle",20),t.ɵɵtext(34),t.ɵɵpipe(35,"translate"),t.ɵɵelementEnd()()()()()()),2&e&&(t.ɵɵproperty("formGroup",n.createRelationConfigForm),t.ɵɵadvance(9),t.ɵɵproperty("ngForOf",n.directionTypes),t.ɵɵadvance(6),t.ɵɵproperty("allowedEntityTypes",n.allowedEntityTypes),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.createRelationConfigForm.get("entityType").value&&n.createRelationConfigForm.get("entityType").value!==n.entityType.TENANT),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.createRelationConfigForm.get("entityType").value===n.entityType.DEVICE||n.createRelationConfigForm.get("entityType").value===n.entityType.ASSET),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.createRelationConfigForm.get("entityType").value===n.entityType.CUSTOMER||n.createRelationConfigForm.get("entityType").value===n.entityType.DEVICE||n.createRelationConfigForm.get("entityType").value===n.entityType.ASSET),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.createRelationConfigForm.get("entityType").value===n.entityType.CUSTOMER||n.createRelationConfigForm.get("entityType").value===n.entityType.DEVICE||n.createRelationConfigForm.get("entityType").value===n.entityType.ASSET),t.ɵɵadvance(7),t.ɵɵpropertyInterpolate("tb-hint-tooltip-icon",t.ɵɵpipeBind1(27,11,"tb.rulenode.remove-current-relations-hint")),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(30,13,"tb.rulenode.remove-current-relations")," "),t.ɵɵadvance(2),t.ɵɵpropertyInterpolate("tb-hint-tooltip-icon",t.ɵɵpipeBind1(32,15,"tb.rulenode.change-originator-to-related-entity-hint")),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(35,17,"tb.rulenode.change-originator-to-related-entity")," "))},dependencies:t.ɵɵgetComponentDepsFactory(He),encapsulation:2})}}function ze(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",13),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=n.$implicit,r=t.ɵɵnextContext();t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,2,r.directionTypeTranslations.get(e))," ")}}function $e(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-form-field",18)(1,"mat-label"),t.ɵɵtext(2),t.ɵɵpipe(3,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(4,"input",19),t.ɵɵelementEnd()),2&e){const e=t.ɵɵnextContext(2);t.ɵɵadvance(2),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(3,1,e.entityTypeNamePatternTranslation.get(e.deleteRelationConfigForm.get("entityType").value)))}}function Qe(e,n){1&e&&t.ɵɵelement(0,"tb-example-hint",20),2&e&&t.ɵɵproperty("hintText","tb.rulenode.kv-map-single-pattern-hint")}function Je(e,n){if(1&e&&(t.ɵɵelementStart(0,"div")(1,"div",14),t.ɵɵelement(2,"tb-entity-type-select",15),t.ɵɵtemplate(3,$e,5,3,"mat-form-field",16),t.ɵɵelementEnd(),t.ɵɵtemplate(4,Qe,1,1,"tb-example-hint",17),t.ɵɵelementEnd()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(2),t.ɵɵproperty("allowedEntityTypes",e.allowedEntityTypes),t.ɵɵadvance(),t.ɵɵproperty("ngIf",e.deleteRelationConfigForm.get("entityType").value&&e.deleteRelationConfigForm.get("entityType").value!==e.entityType.TENANT),t.ɵɵadvance(),t.ɵɵproperty("ngIf",e.deleteRelationConfigForm.get("entityType").value&&e.deleteRelationConfigForm.get("entityType").value!==e.entityType.TENANT)}}e("CreateRelationConfigComponent",He);class Ye extends i{constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(d),this.directionTypeTranslations=new Map([[d.FROM,"tb.rulenode.del-relation-direction-from"],[d.TO,"tb.rulenode.del-relation-direction-to"]]),this.entityTypeNamePatternTranslation=new Map([[u.DEVICE,"tb.rulenode.device-name-pattern"],[u.ASSET,"tb.rulenode.asset-name-pattern"],[u.ENTITY_VIEW,"tb.rulenode.entity-view-name-pattern"],[u.CUSTOMER,"tb.rulenode.customer-title-pattern"],[u.USER,"tb.rulenode.user-name-pattern"],[u.DASHBOARD,"tb.rulenode.dashboard-name-pattern"],[u.EDGE,"tb.rulenode.edge-name-pattern"]]),this.entityType=u,this.allowedEntityTypes=[u.DEVICE,u.ASSET,u.ENTITY_VIEW,u.TENANT,u.CUSTOMER,u.USER,u.DASHBOARD,u.EDGE]}configForm(){return this.deleteRelationConfigForm}onConfigurationSet(e){this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[N.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[N.required]]})}validatorTriggers(){return["deleteForSingleEntity","entityType"]}updateValidators(e){const t=this.deleteRelationConfigForm.get("deleteForSingleEntity").value,n=this.deleteRelationConfigForm.get("entityType").value;t?this.deleteRelationConfigForm.get("entityType").setValidators([N.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),t&&n&&n!==u.TENANT?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([N.required,N.pattern(/.*\S.*/)]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})}prepareOutputConfig(e){return e.entityNamePattern=e.entityNamePattern?e.entityNamePattern.trim():null,e}static{this.ɵfac=function(e){return new(e||Ye)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Ye,selectors:[["tb-action-node-delete-relation-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:18,vars:9,consts:[[1,"tb-form-panel","no-padding","no-border",3,"formGroup"],[1,"tb-form-panel","stroked","no-padding-bottom"],["translate","",1,"tb-form-panel-title"],[1,"flex","flex-col"],["hideRequiredMarker","",1,"mat-block"],["translate",""],["required","","matInput","","formControlName","direction"],[3,"value",4,"ngFor","ngForOf"],["required","","formControlName","relationType"],[1,"tb-form-panel","stroked"],[1,"tb-form-row","no-border","no-padding",3,"tb-hint-tooltip-icon"],["formControlName","deleteForSingleEntity",1,"mat-slide"],[4,"ngIf"],[3,"value"],[1,"flex","flex-row","gap-2.5"],["showLabel","","required","","formControlName","entityType",1,"flex-1",3,"allowedEntityTypes"],["class","mat-block flex-1",4,"ngIf"],[3,"hintText",4,"ngIf"],[1,"mat-block","flex-1"],["required","","matInput","","formControlName","entityNamePattern"],[3,"hintText"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"div",1)(2,"div",2),t.ɵɵtext(3,"tb.rulenode.relation-parameters"),t.ɵɵelementEnd(),t.ɵɵelementStart(4,"div",3)(5,"mat-form-field",4)(6,"mat-label",5),t.ɵɵtext(7,"relation.direction"),t.ɵɵelementEnd(),t.ɵɵelementStart(8,"mat-select",6),t.ɵɵtemplate(9,ze,3,4,"mat-option",7),t.ɵɵelementEnd()(),t.ɵɵelement(10,"tb-relation-type-autocomplete",8),t.ɵɵelementEnd()(),t.ɵɵelementStart(11,"div",9)(12,"div",10),t.ɵɵpipe(13,"translate"),t.ɵɵelementStart(14,"mat-slide-toggle",11),t.ɵɵtext(15),t.ɵɵpipe(16,"translate"),t.ɵɵelementEnd()(),t.ɵɵtemplate(17,Je,5,3,"div",12),t.ɵɵelementEnd()()),2&e&&(t.ɵɵproperty("formGroup",n.deleteRelationConfigForm),t.ɵɵadvance(9),t.ɵɵproperty("ngForOf",n.directionTypes),t.ɵɵadvance(3),t.ɵɵpropertyInterpolate("tb-hint-tooltip-icon",t.ɵɵpipeBind1(13,5,"tb.rulenode.delete-relation-with-specific-entity-hint")),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(16,7,"tb.rulenode.delete-relation-with-specific-entity")," "),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",n.deleteRelationConfigForm.get("deleteForSingleEntity").value))},dependencies:t.ɵɵgetComponentDepsFactory(Ye),encapsulation:2})}}e("DeleteRelationConfigComponent",Ye);class We extends i{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.deviceProfile}onConfigurationSet(e){this.deviceProfile=this.fb.group({persistAlarmRulesState:[!!e&&e.persistAlarmRulesState],fetchAlarmRulesStateOnStart:[!!e&&e.fetchAlarmRulesStateOnStart]})}validatorTriggers(){return["persistAlarmRulesState"]}updateValidators(e){this.deviceProfile.get("persistAlarmRulesState").value?this.deviceProfile.get("fetchAlarmRulesStateOnStart").enable({emitEvent:!1}):(this.deviceProfile.get("fetchAlarmRulesStateOnStart").setValue(!1,{emitEvent:!1}),this.deviceProfile.get("fetchAlarmRulesStateOnStart").disable({emitEvent:!1})),this.deviceProfile.get("fetchAlarmRulesStateOnStart").updateValueAndValidity({emitEvent:e})}static{this.ɵfac=function(e){return new(e||We)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:We,selectors:[["tb-device-profile-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:13,vars:13,consts:[[1,"tb-form-panel","stroked",3,"formGroup"],["translate","",1,"tb-form-hint","tb-primary-fill"],[1,"tb-form-row","no-border","no-padding","slide-toggle",3,"tb-hint-tooltip-icon"],["formControlName","persistAlarmRulesState",1,"mat-slide"],["formControlName","fetchAlarmRulesStateOnStart",1,"mat-slide"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"div",1),t.ɵɵtext(2,"tb.rulenode.device-profile-node-hint"),t.ɵɵelementEnd(),t.ɵɵelementStart(3,"div",2),t.ɵɵpipe(4,"translate"),t.ɵɵelementStart(5,"mat-slide-toggle",3),t.ɵɵtext(6),t.ɵɵpipe(7,"translate"),t.ɵɵelementEnd()(),t.ɵɵelementStart(8,"div",2),t.ɵɵpipe(9,"translate"),t.ɵɵelementStart(10,"mat-slide-toggle",4),t.ɵɵtext(11),t.ɵɵpipe(12,"translate"),t.ɵɵelementEnd()()()),2&e&&(t.ɵɵproperty("formGroup",n.deviceProfile),t.ɵɵadvance(3),t.ɵɵpropertyInterpolate("tb-hint-tooltip-icon",t.ɵɵpipeBind1(4,5,"tb.rulenode.persist-alarm-rules-hint")),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(7,7,"tb.rulenode.persist-alarm-rules")," "),t.ɵɵadvance(2),t.ɵɵpropertyInterpolate("tb-hint-tooltip-icon",t.ɵɵpipeBind1(9,9,"tb.rulenode.fetch-alarm-rules-hint")),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(12,11,"tb.rulenode.fetch-alarm-rules")," "))},dependencies:t.ɵɵgetComponentDepsFactory(We),encapsulation:2})}}e("DeviceProfileConfigComponent",We);const Xe=["jsFuncComponent"],Ze=["tbelFuncComponent"],et=()=>["prevMsg","prevMetadata","prevMsgType"];function tt(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.message-count-required")," "))}function nt(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.min-message-count-message")," "))}function rt(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.generation-frequency-required")," "))}function at(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.min-generation-frequency-message")," "))}function it(e,n){if(1&e&&(t.ɵɵelementStart(0,"tb-toggle-select",22)(1,"tb-toggle-option",23),t.ɵɵtext(2),t.ɵɵpipe(3,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(4,"tb-toggle-option",23),t.ɵɵtext(5),t.ɵɵpipe(6,"translate"),t.ɵɵelementEnd()()),2&e){const e=t.ɵɵnextContext(2);t.ɵɵadvance(),t.ɵɵproperty("value",e.scriptLanguage.TBEL),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(3,4,"tb.rulenode.script-lang-tbel")," "),t.ɵɵadvance(2),t.ɵɵproperty("value",e.scriptLanguage.JS),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(6,6,"tb.rulenode.script-lang-js")," ")}}function ot(e,n){if(1&e){const e=t.ɵɵgetCurrentView();t.ɵɵelementStart(0,"tb-js-func",18,0),t.ɵɵtemplate(2,it,7,8,"tb-toggle-select",19),t.ɵɵelementStart(3,"button",20),t.ɵɵpipe(4,"translate"),t.ɵɵlistener("click",(function(){t.ɵɵrestoreView(e);const n=t.ɵɵnextContext();return t.ɵɵresetView(n.testScript())})),t.ɵɵelementStart(5,"mat-icon",21),t.ɵɵtext(6,"bug_report"),t.ɵɵelementEnd()()()}if(2&e){const e=t.ɵɵnextContext();t.ɵɵproperty("functionArgs",t.ɵɵpureFunction0(5,et)),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",e.tbelEnabled),t.ɵɵadvance(),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(4,3,e.testScriptLabel))}}function lt(e,n){if(1&e){const e=t.ɵɵgetCurrentView();t.ɵɵelementStart(0,"tb-js-func",24,1)(2,"tb-toggle-select",22)(3,"tb-toggle-option",23),t.ɵɵtext(4),t.ɵɵpipe(5,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(6,"tb-toggle-option",23),t.ɵɵtext(7),t.ɵɵpipe(8,"translate"),t.ɵɵelementEnd()(),t.ɵɵelementStart(9,"button",20),t.ɵɵpipe(10,"translate"),t.ɵɵlistener("click",(function(){t.ɵɵrestoreView(e);const n=t.ɵɵnextContext();return t.ɵɵresetView(n.testScript())})),t.ɵɵelementStart(11,"mat-icon",21),t.ɵɵtext(12,"bug_report"),t.ɵɵelementEnd()()()}if(2&e){const e=t.ɵɵnextContext();t.ɵɵproperty("functionArgs",t.ɵɵpureFunction0(14,et))("disableUndefinedCheck",!0)("scriptLanguage",e.scriptLanguage.TBEL),t.ɵɵadvance(3),t.ɵɵproperty("value",e.scriptLanguage.TBEL),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(5,8,"tb.rulenode.script-lang-tbel")," "),t.ɵɵadvance(2),t.ɵɵproperty("value",e.scriptLanguage.JS),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(8,10,"tb.rulenode.script-lang-js")," "),t.ɵɵadvance(2),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(10,12,e.testScriptLabel))}}class st extends i{constructor(e,t,r,a){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=r,this.translate=a,this.tbelEnabled=D(this.store).tbelEnabled,this.scriptLanguage=s,this.changeScript=new n,this.allowedEntityTypes=[u.DEVICE,u.ASSET,u.ENTITY_VIEW,u.CUSTOMER,u.USER,u.DASHBOARD],this.additionEntityTypes={TENANT:this.translate.instant("tb.rulenode.current-tenant"),RULE_NODE:this.translate.instant("tb.rulenode.current-rule-node")},this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-generator-function"}configForm(){return this.generatorConfigForm}onConfigurationSet(e){this.generatorConfigForm=this.fb.group({msgCount:[e?e.msgCount:null,[N.required,N.min(0)]],periodInSeconds:[e?e.periodInSeconds:null,[N.required,N.min(1)]],originator:[e?e.originator:{id:null,entityType:u.RULE_NODE},[]],scriptLang:[e?e.scriptLang:s.JS,[N.required]],jsScript:[e?e.jsScript:null,[]],tbelScript:[e?e.tbelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.generatorConfigForm.get("scriptLang").value;t!==s.TBEL||this.tbelEnabled||(t=s.JS,this.generatorConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.generatorConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.generatorConfigForm.get("jsScript").setValidators(t===s.JS?[N.required]:[]),this.generatorConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.generatorConfigForm.get("tbelScript").setValidators(t===s.TBEL?[N.required]:[]),this.generatorConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return{msgCount:P(e?.msgCount)?e?.msgCount:0,periodInSeconds:P(e?.periodInSeconds)?e?.periodInSeconds:1,originator:{id:P(e?.originatorId)?e?.originatorId:null,entityType:P(e?.originatorType)?e?.originatorType:u.RULE_NODE},scriptLang:P(e?.scriptLang)?e?.scriptLang:s.JS,tbelScript:P(e?.tbelScript)?e?.tbelScript:null,jsScript:P(e?.jsScript)?e?.jsScript:null}}prepareOutputConfig(e){return e.originator?(e.originatorId=e.originator.id,e.originatorType=e.originator.entityType):(e.originatorId=null,e.originatorType=null),delete e.originator,e}testScript(e){const t=this.generatorConfigForm.get("scriptLang").value,n=t===s.JS?"jsScript":"tbelScript",r=t===s.JS?"rulenode/generator_node_script_fn":"rulenode/tbel/generator_node_script_fn",a=this.generatorConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(a,"generate",this.translate.instant("tb.rulenode.generator"),"Generate",["prevMsg","prevMetadata","prevMsgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.generatorConfigForm.get(n).setValue(e),this.changeScript.emit())}))}onValidate(){this.generatorConfigForm.get("scriptLang").value===s.JS&&this.jsFuncComponent.validateOnSubmit()}static{this.ɵfac=function(e){return new(e||st)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder),t.ɵɵdirectiveInject(L.NodeScriptTestService),t.ɵɵdirectiveInject(K.TranslateService))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:st,selectors:[["tb-action-node-generator-config"]],viewQuery:function(e,n){if(1&e&&(t.ɵɵviewQuery(Xe,5),t.ɵɵviewQuery(Ze,5)),2&e){let e;t.ɵɵqueryRefresh(e=t.ɵɵloadQuery())&&(n.jsFuncComponent=e.first),t.ɵɵqueryRefresh(e=t.ɵɵloadQuery())&&(n.tbelFuncComponent=e.first)}},features:[t.ɵɵInheritDefinitionFeature],decls:32,vars:12,consts:[["jsFuncComponent",""],["tbelFuncComponent",""],[1,"tb-form-panel","no-border","no-padding",3,"formGroup"],[1,"tb-form-panel","no-padding-bottom","stroked"],["translate","",1,"tb-form-panel-title"],[1,"tb-form-row","no-border","no-padding","tb-standard-fields","column-xs"],[1,"flex"],["translate",""],["required","","type","number","min","0","step","1","matInput","","formControlName","msgCount"],[4,"ngIf"],["required","","type","number","min","1","step","1","matInput","","formControlName","periodInSeconds"],["required","true","useAliasEntityTypes","true","formControlName","originator",1,"flex-1",3,"allowedEntityTypes","additionEntityTypes"],[1,"tb-form-panel","stroked"],["expanded","",1,"tb-settings"],["formControlName","jsScript","functionName","Generate","helpId","rulenode/generator_node_script_fn","noValidate","true",3,"functionArgs",4,"ngIf"],["formControlName","tbelScript","functionName","Generate","helpId","rulenode/tbel/generator_node_script_fn","noValidate","true",3,"functionArgs","disableUndefinedCheck","scriptLanguage",4,"ngIf"],[1,"flex","flex-row",2,"padding-bottom","16px"],["mat-button","","mat-raised-button","","color","primary",3,"click"],["formControlName","jsScript","functionName","Generate","helpId","rulenode/generator_node_script_fn","noValidate","true",3,"functionArgs"],["toolbarPrefixButton","","formControlName","scriptLang","appearance","fill",4,"ngIf"],["toolbarSuffixButton","","mat-icon-button","","matTooltipPosition","above",1,"tb-mat-32",3,"click","matTooltip"],["color","primary",1,"material-icons"],["toolbarPrefixButton","","formControlName","scriptLang","appearance","fill"],[3,"value"],["formControlName","tbelScript","functionName","Generate","helpId","rulenode/tbel/generator_node_script_fn","noValidate","true",3,"functionArgs","disableUndefinedCheck","scriptLanguage"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",2)(1,"div",3)(2,"div",4),t.ɵɵtext(3,"tb.rulenode.generation-parameters"),t.ɵɵelementEnd(),t.ɵɵelementStart(4,"div",5)(5,"mat-form-field",6)(6,"mat-label",7),t.ɵɵtext(7,"tb.rulenode.message-count"),t.ɵɵelementEnd(),t.ɵɵelement(8,"input",8),t.ɵɵtemplate(9,tt,3,3,"mat-error",9)(10,nt,3,3,"mat-error",9),t.ɵɵelementEnd(),t.ɵɵelementStart(11,"mat-form-field",6)(12,"mat-label",7),t.ɵɵtext(13,"tb.rulenode.generation-frequency-seconds"),t.ɵɵelementEnd(),t.ɵɵelement(14,"input",10),t.ɵɵtemplate(15,rt,3,3,"mat-error",9)(16,at,3,3,"mat-error",9),t.ɵɵelementEnd()()(),t.ɵɵelementStart(17,"div",3)(18,"div",4),t.ɵɵtext(19,"tb.rulenode.originator"),t.ɵɵelementEnd(),t.ɵɵelement(20,"tb-entity-select",11),t.ɵɵelementEnd(),t.ɵɵelementStart(21,"div",12)(22,"mat-expansion-panel",13)(23,"mat-expansion-panel-header")(24,"mat-panel-title",7),t.ɵɵtext(25,"tb.rulenode.generator-function"),t.ɵɵelementEnd()(),t.ɵɵtemplate(26,ot,7,6,"tb-js-func",14)(27,lt,13,15,"tb-js-func",15),t.ɵɵelementStart(28,"div",16)(29,"button",17),t.ɵɵlistener("click",(function(){return n.testScript()})),t.ɵɵtext(30),t.ɵɵpipe(31,"translate"),t.ɵɵelementEnd()()()()()),2&e&&(t.ɵɵproperty("formGroup",n.generatorConfigForm),t.ɵɵadvance(9),t.ɵɵproperty("ngIf",n.generatorConfigForm.get("msgCount").hasError("required")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.generatorConfigForm.get("msgCount").hasError("min")),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",n.generatorConfigForm.get("periodInSeconds").hasError("required")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.generatorConfigForm.get("periodInSeconds").hasError("min")),t.ɵɵadvance(4),t.ɵɵproperty("allowedEntityTypes",n.allowedEntityTypes)("additionEntityTypes",n.additionEntityTypes),t.ɵɵadvance(6),t.ɵɵproperty("ngIf",n.generatorConfigForm.get("scriptLang").value===n.scriptLanguage.JS),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.generatorConfigForm.get("scriptLang").value===n.scriptLanguage.TBEL),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(31,10,n.testScriptLabel)," "))},dependencies:t.ɵɵgetComponentDepsFactory(st),styles:["[_nghost-%COMP%] .mat-button-toggle-group{min-width:120px;height:24px!important}[_nghost-%COMP%] .mat-button-toggle-group .mat-button-toggle{font-size:0}[_nghost-%COMP%] .mat-button-toggle-group .mat-button-toggle .mat-button-toggle-button{height:20px!important;line-height:20px!important;border:none!important}[_nghost-%COMP%] .mat-button-toggle-group .mat-button-toggle .mat-button-toggle-button .mat-button-toggle-label-content{font-size:14px!important;line-height:20px!important}@media screen and (min-width: 599px){[_nghost-%COMP%] .tb-entity-select{display:flex;flex-direction:row;gap:16px}}[_nghost-%COMP%] .tb-entity-select tb-entity-type-select{flex:1}[_nghost-%COMP%] .tb-entity-select tb-entity-autocomplete{flex:1}[_nghost-%COMP%] .tb-entity-select tb-entity-autocomplete mat-form-field{width:100%!important}"]})}}var pt;e("GeneratorConfigComponent",st),function(e){e.CUSTOMER="CUSTOMER",e.TENANT="TENANT",e.RELATED="RELATED",e.ALARM_ORIGINATOR="ALARM_ORIGINATOR",e.ENTITY="ENTITY"}(pt||(pt={}));const mt=new Map([[pt.CUSTOMER,"tb.rulenode.originator-customer"],[pt.TENANT,"tb.rulenode.originator-tenant"],[pt.RELATED,"tb.rulenode.originator-related"],[pt.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"],[pt.ENTITY,"tb.rulenode.originator-entity"]]),dt=new Map([[pt.CUSTOMER,"tb.rulenode.originator-customer-desc"],[pt.TENANT,"tb.rulenode.originator-tenant-desc"],[pt.RELATED,"tb.rulenode.originator-related-entity-desc"],[pt.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator-desc"],[pt.ENTITY,"tb.rulenode.originator-entity-by-name-pattern-desc"]]),ut=[c.createdTime,c.name,{value:"type",name:"tb.rulenode.profile-name",keyName:"originatorProfileName"},c.firstName,c.lastName,c.email,c.title,c.country,c.state,c.city,c.address,c.address2,c.zip,c.phone,c.label,{value:"id",name:"tb.rulenode.id",keyName:"id"},{value:"additionalInfo",name:"tb.rulenode.additional-info",keyName:"additionalInfo"}],ct=new Map([["type","profileName"],["createdTime","createdTime"],["name","name"],["firstName","firstName"],["lastName","lastName"],["email","email"],["title","title"],["country","country"],["state","state"],["city","city"],["address","address"],["address2","address2"],["zip","zip"],["phone","phone"],["label","label"],["id","id"],["additionalInfo","additionalInfo"]]);var ft;!function(e){e.CIRCLE="CIRCLE",e.POLYGON="POLYGON"}(ft||(ft={}));const gt=new Map([[ft.CIRCLE,"tb.rulenode.perimeter-circle"],[ft.POLYGON,"tb.rulenode.perimeter-polygon"]]);var ht;!function(e){e.MILLISECONDS="MILLISECONDS",e.SECONDS="SECONDS",e.MINUTES="MINUTES",e.HOURS="HOURS",e.DAYS="DAYS"}(ht||(ht={}));const yt=new Map([[ht.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[ht.SECONDS,"tb.rulenode.time-unit-seconds"],[ht.MINUTES,"tb.rulenode.time-unit-minutes"],[ht.HOURS,"tb.rulenode.time-unit-hours"],[ht.DAYS,"tb.rulenode.time-unit-days"]]);var bt;!function(e){e.METER="METER",e.KILOMETER="KILOMETER",e.FOOT="FOOT",e.MILE="MILE",e.NAUTICAL_MILE="NAUTICAL_MILE"}(bt||(bt={}));const vt=new Map([[bt.METER,"tb.rulenode.range-unit-meter"],[bt.KILOMETER,"tb.rulenode.range-unit-kilometer"],[bt.FOOT,"tb.rulenode.range-unit-foot"],[bt.MILE,"tb.rulenode.range-unit-mile"],[bt.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]);var xt;!function(e){e.ID="ID",e.TITLE="TITLE",e.COUNTRY="COUNTRY",e.STATE="STATE",e.CITY="CITY",e.ZIP="ZIP",e.ADDRESS="ADDRESS",e.ADDRESS2="ADDRESS2",e.PHONE="PHONE",e.EMAIL="EMAIL",e.ADDITIONAL_INFO="ADDITIONAL_INFO"}(xt||(xt={}));const Ct=new Map([[xt.ID,"tb.rulenode.entity-details-id"],[xt.TITLE,"tb.rulenode.entity-details-title"],[xt.COUNTRY,"tb.rulenode.entity-details-country"],[xt.STATE,"tb.rulenode.entity-details-state"],[xt.CITY,"tb.rulenode.entity-details-city"],[xt.ZIP,"tb.rulenode.entity-details-zip"],[xt.ADDRESS,"tb.rulenode.entity-details-address"],[xt.ADDRESS2,"tb.rulenode.entity-details-address2"],[xt.PHONE,"tb.rulenode.entity-details-phone"],[xt.EMAIL,"tb.rulenode.entity-details-email"],[xt.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]);var St;!function(e){e.FIRST="FIRST",e.LAST="LAST",e.ALL="ALL"}(St||(St={}));const Tt=new Map([[St.FIRST,"tb.rulenode.first"],[St.LAST,"tb.rulenode.last"],[St.ALL,"tb.rulenode.all"]]),It=new Map([[St.FIRST,"tb.rulenode.first-mode-hint"],[St.LAST,"tb.rulenode.last-mode-hint"],[St.ALL,"tb.rulenode.all-mode-hint"]]);var Et,Ft;!function(e){e.ASC="ASC",e.DESC="DESC"}(Et||(Et={})),function(e){e.ATTRIBUTES="ATTRIBUTES",e.LATEST_TELEMETRY="LATEST_TELEMETRY",e.FIELDS="FIELDS"}(Ft||(Ft={}));const qt=new Map([[Ft.ATTRIBUTES,"tb.rulenode.attributes"],[Ft.LATEST_TELEMETRY,"tb.rulenode.latest-telemetry"],[Ft.FIELDS,"tb.rulenode.fields"]]),At=new Map([[Ft.ATTRIBUTES,"tb.rulenode.add-mapped-attribute-to"],[Ft.LATEST_TELEMETRY,"tb.rulenode.add-mapped-latest-telemetry-to"],[Ft.FIELDS,"tb.rulenode.add-mapped-fields-to"]]),kt=new Map([[Et.ASC,"tb.rulenode.ascending"],[Et.DESC,"tb.rulenode.descending"]]);var Nt;!function(e){e.STANDARD="STANDARD",e.FIFO="FIFO"}(Nt||(Nt={}));const wt=new Map([[Nt.STANDARD,"tb.rulenode.sqs-queue-standard"],[Nt.FIFO,"tb.rulenode.sqs-queue-fifo"]]),Mt=["anonymous","basic","cert.PEM"],Bt=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),Vt=["sas","cert.PEM"],Ot=new Map([["sas","tb.rulenode.credentials-sas"],["cert.PEM","tb.rulenode.credentials-pem"]]);var Dt;!function(e){e.GET="GET",e.POST="POST",e.PUT="PUT",e.DELETE="DELETE"}(Dt||(Dt={}));const Lt=["US-ASCII","ISO-8859-1","UTF-8","UTF-16BE","UTF-16LE","UTF-16"],Pt=new Map([["US-ASCII","tb.rulenode.charset-us-ascii"],["ISO-8859-1","tb.rulenode.charset-iso-8859-1"],["UTF-8","tb.rulenode.charset-utf-8"],["UTF-16BE","tb.rulenode.charset-utf-16be"],["UTF-16LE","tb.rulenode.charset-utf-16le"],["UTF-16","tb.rulenode.charset-utf-16"]]);var Rt;!function(e){e.CUSTOM="CUSTOM",e.ADD="ADD",e.SUB="SUB",e.MULT="MULT",e.DIV="DIV",e.SIN="SIN",e.SINH="SINH",e.COS="COS",e.COSH="COSH",e.TAN="TAN",e.TANH="TANH",e.ACOS="ACOS",e.ASIN="ASIN",e.ATAN="ATAN",e.ATAN2="ATAN2",e.EXP="EXP",e.EXPM1="EXPM1",e.SQRT="SQRT",e.CBRT="CBRT",e.GET_EXP="GET_EXP",e.HYPOT="HYPOT",e.LOG="LOG",e.LOG10="LOG10",e.LOG1P="LOG1P",e.CEIL="CEIL",e.FLOOR="FLOOR",e.FLOOR_DIV="FLOOR_DIV",e.FLOOR_MOD="FLOOR_MOD",e.ABS="ABS",e.MIN="MIN",e.MAX="MAX",e.POW="POW",e.SIGNUM="SIGNUM",e.RAD="RAD",e.DEG="DEG"}(Rt||(Rt={}));const _t=new Map([[Rt.CUSTOM,{value:Rt.CUSTOM,name:"Custom Function",description:"Use this function to specify complex mathematical expression.",minArgs:1,maxArgs:16}],[Rt.ADD,{value:Rt.ADD,name:"Addition",description:"x + y",minArgs:2,maxArgs:2}],[Rt.SUB,{value:Rt.SUB,name:"Subtraction",description:"x - y",minArgs:2,maxArgs:2}],[Rt.MULT,{value:Rt.MULT,name:"Multiplication",description:"x * y",minArgs:2,maxArgs:2}],[Rt.DIV,{value:Rt.DIV,name:"Division",description:"x / y",minArgs:2,maxArgs:2}],[Rt.SIN,{value:Rt.SIN,name:"Sine",description:"Returns the trigonometric sine of an angle in radians.",minArgs:1,maxArgs:1}],[Rt.SINH,{value:Rt.SINH,name:"Hyperbolic sine",description:"Returns the hyperbolic sine of an argument.",minArgs:1,maxArgs:1}],[Rt.COS,{value:Rt.COS,name:"Cosine",description:"Returns the trigonometric cosine of an angle in radians.",minArgs:1,maxArgs:1}],[Rt.COSH,{value:Rt.COSH,name:"Hyperbolic cosine",description:"Returns the hyperbolic cosine of an argument.",minArgs:1,maxArgs:1}],[Rt.TAN,{value:Rt.TAN,name:"Tangent",description:"Returns the trigonometric tangent of an angle in radians",minArgs:1,maxArgs:1}],[Rt.TANH,{value:Rt.TANH,name:"Hyperbolic tangent",description:"Returns the hyperbolic tangent of an argument",minArgs:1,maxArgs:1}],[Rt.ACOS,{value:Rt.ACOS,name:"Arc cosine",description:"Returns the arc cosine of an argument",minArgs:1,maxArgs:1}],[Rt.ASIN,{value:Rt.ASIN,name:"Arc sine",description:"Returns the arc sine of an argument",minArgs:1,maxArgs:1}],[Rt.ATAN,{value:Rt.ATAN,name:"Arc tangent",description:"Returns the arc tangent of an argument",minArgs:1,maxArgs:1}],[Rt.ATAN2,{value:Rt.ATAN2,name:"2-argument arc tangent",description:"Returns the angle theta from the conversion of rectangular coordinates (x, y) to polar coordinates (r, theta)",minArgs:2,maxArgs:2}],[Rt.EXP,{value:Rt.EXP,name:"Exponential",description:"Returns Euler's number e raised to the power of an argument",minArgs:1,maxArgs:1}],[Rt.EXPM1,{value:Rt.EXPM1,name:"Exponential minus one",description:"Returns Euler's number e raised to the power of an argument minus one",minArgs:1,maxArgs:1}],[Rt.SQRT,{value:Rt.SQRT,name:"Square",description:"Returns the correctly rounded positive square root of an argument",minArgs:1,maxArgs:1}],[Rt.CBRT,{value:Rt.CBRT,name:"Cube root",description:"Returns the cube root of an argument",minArgs:1,maxArgs:1}],[Rt.GET_EXP,{value:Rt.GET_EXP,name:"Get exponent",description:"Returns the unbiased exponent used in the representation of an argument",minArgs:1,maxArgs:1}],[Rt.HYPOT,{value:Rt.HYPOT,name:"Square root",description:"Returns the square root of the squares of the arguments",minArgs:2,maxArgs:2}],[Rt.LOG,{value:Rt.LOG,name:"Logarithm",description:"Returns the natural logarithm of an argument",minArgs:1,maxArgs:1}],[Rt.LOG10,{value:Rt.LOG10,name:"Base 10 logarithm",description:"Returns the base 10 logarithm of an argument",minArgs:1,maxArgs:1}],[Rt.LOG1P,{value:Rt.LOG1P,name:"Logarithm of the sum",description:"Returns the natural logarithm of the sum of an argument",minArgs:1,maxArgs:1}],[Rt.CEIL,{value:Rt.CEIL,name:"Ceiling",description:"Returns the smallest (closest to negative infinity) of an argument",minArgs:1,maxArgs:1}],[Rt.FLOOR,{value:Rt.FLOOR,name:"Floor",description:"Returns the largest (closest to positive infinity) of an argument",minArgs:1,maxArgs:1}],[Rt.FLOOR_DIV,{value:Rt.FLOOR_DIV,name:"Floor division",description:"Returns the largest (closest to positive infinity) of the arguments",minArgs:2,maxArgs:2}],[Rt.FLOOR_MOD,{value:Rt.FLOOR_MOD,name:"Floor modulus",description:"Returns the floor modulus of the arguments",minArgs:2,maxArgs:2}],[Rt.ABS,{value:Rt.ABS,name:"Absolute",description:"Returns the absolute value of an argument",minArgs:1,maxArgs:1}],[Rt.MIN,{value:Rt.MIN,name:"Min",description:"Returns the smaller of the arguments",minArgs:2,maxArgs:2}],[Rt.MAX,{value:Rt.MAX,name:"Max",description:"Returns the greater of the arguments",minArgs:2,maxArgs:2}],[Rt.POW,{value:Rt.POW,name:"Raise to a power",description:"Returns the value of the first argument raised to the power of the second argument",minArgs:2,maxArgs:2}],[Rt.SIGNUM,{value:Rt.SIGNUM,name:"Sign of a real number",description:"Returns the signum function of the argument",minArgs:1,maxArgs:1}],[Rt.RAD,{value:Rt.RAD,name:"Radian",description:"Converts an angle measured in degrees to an approximately equivalent angle measured in radians",minArgs:1,maxArgs:1}],[Rt.DEG,{value:Rt.DEG,name:"Degrees",description:"Converts an angle measured in radians to an approximately equivalent angle measured in degrees.",minArgs:1,maxArgs:1}]]);var jt,Gt,Kt;!function(e){e.MESSAGE_BODY="MESSAGE_BODY",e.MESSAGE_METADATA="MESSAGE_METADATA",e.ATTRIBUTE="ATTRIBUTE",e.TIME_SERIES="TIME_SERIES",e.CONSTANT="CONSTANT"}(jt||(jt={})),function(e){e.MESSAGE_BODY="MESSAGE_BODY",e.MESSAGE_METADATA="MESSAGE_METADATA",e.ATTRIBUTE="ATTRIBUTE",e.TIME_SERIES="TIME_SERIES"}(Gt||(Gt={})),function(e){e.DATA="DATA",e.METADATA="METADATA"}(Kt||(Kt={}));const Ut=new Map([[Kt.DATA,"tb.rulenode.message-to-metadata"],[Kt.METADATA,"tb.rulenode.metadata-to-message"]]),Ht=(new Map([[Kt.DATA,"tb.rulenode.from-message"],[Kt.METADATA,"tb.rulenode.from-metadata"]]),new Map([[Kt.DATA,"tb.rulenode.message"],[Kt.METADATA,"tb.rulenode.metadata"]])),zt=new Map([[Kt.DATA,"tb.rulenode.message"],[Kt.METADATA,"tb.rulenode.message-metadata"]]),$t=new Map([[jt.MESSAGE_BODY,{name:"tb.rulenode.message-body-type",description:"Fetch argument value from incoming message"}],[jt.MESSAGE_METADATA,{name:"tb.rulenode.message-metadata-type",description:"Fetch argument value from incoming message metadata"}],[jt.ATTRIBUTE,{name:"tb.rulenode.attribute-type",description:"Fetch attribute value from database"}],[jt.TIME_SERIES,{name:"tb.rulenode.time-series-type",description:"Fetch latest time-series value from database"}],[jt.CONSTANT,{name:"tb.rulenode.constant-type",description:"Define constant value"}]]),Qt=new Map([[Gt.MESSAGE_BODY,{name:"tb.rulenode.message-body-type",description:"Add result to the outgoing message"}],[Gt.MESSAGE_METADATA,{name:"tb.rulenode.message-metadata-type",description:"Add result to the outgoing message metadata"}],[Gt.ATTRIBUTE,{name:"tb.rulenode.attribute-type",description:"Store result as an entity attribute in the database"}],[Gt.TIME_SERIES,{name:"tb.rulenode.time-series-type",description:"Store result as an entity time-series in the database"}]]),Jt=["x","y","z","a","b","c","d","k","l","m","n","o","p","r","s","t"];var Yt,Wt;!function(e){e.SHARED_SCOPE="SHARED_SCOPE",e.SERVER_SCOPE="SERVER_SCOPE",e.CLIENT_SCOPE="CLIENT_SCOPE"}(Yt||(Yt={})),function(e){e.SHARED_SCOPE="SHARED_SCOPE",e.SERVER_SCOPE="SERVER_SCOPE"}(Wt||(Wt={}));const Xt=new Map([[Yt.SHARED_SCOPE,"tb.rulenode.shared-scope"],[Yt.SERVER_SCOPE,"tb.rulenode.server-scope"],[Yt.CLIENT_SCOPE,"tb.rulenode.client-scope"]]);var Zt;!function(e){e.ON_FIRST_MESSAGE="ON_FIRST_MESSAGE",e.ON_EACH_MESSAGE="ON_EACH_MESSAGE"}(Zt||(Zt={}));const en=new Map([[Zt.ON_EACH_MESSAGE,{value:!0,name:"tb.rulenode.presence-monitoring-strategy-on-each-message"}],[Zt.ON_FIRST_MESSAGE,{value:!1,name:"tb.rulenode.presence-monitoring-strategy-on-first-message"}]]),tn=2147483648,nn=e=>({perimeterKeyName:e});function rn(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.latitude-field-name-required")," "))}function an(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.longitude-field-name-required")," "))}function on(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",22),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=n.$implicit,r=t.ɵɵnextContext();t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,2,r.perimeterTypeTranslationMap.get(e))," ")}}function ln(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.perimeter-key-name-required")," "))}function sn(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-form-field",23)(1,"mat-label"),t.ɵɵtext(2),t.ɵɵpipe(3,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(4,"input",24),t.ɵɵtemplate(5,ln,3,3,"mat-error",6),t.ɵɵelementStart(6,"mat-hint"),t.ɵɵtext(7),t.ɵɵpipe(8,"translate"),t.ɵɵelementEnd()()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(2),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(3,3,"tb.rulenode.perimeter-key-name")),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",e.geoActionConfigForm.get("perimeterKeyName").hasError("required")),t.ɵɵadvance(2),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(8,5,"tb.rulenode.perimeter-key-name-hint"))}}function pn(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.circle-center-latitude-required")," "))}function mn(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.circle-center-longitude-required")," "))}function dn(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.range-required")," "))}function un(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",22),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=n.$implicit,r=t.ɵɵnextContext(2);t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,2,r.rangeUnitTranslationMap.get(e))," ")}}function cn(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.range-units-required")," "))}function fn(e,n){if(1&e&&(t.ɵɵelementStart(0,"div",9)(1,"div",3)(2,"mat-form-field",25)(3,"mat-label"),t.ɵɵtext(4),t.ɵɵpipe(5,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(6,"input",26),t.ɵɵtemplate(7,pn,3,3,"mat-error",6),t.ɵɵelementEnd(),t.ɵɵelementStart(8,"mat-form-field",25)(9,"mat-label"),t.ɵɵtext(10),t.ɵɵpipe(11,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(12,"input",27),t.ɵɵtemplate(13,mn,3,3,"mat-error",6),t.ɵɵelementEnd()(),t.ɵɵelementStart(14,"div",3)(15,"mat-form-field",25)(16,"mat-label"),t.ɵɵtext(17),t.ɵɵpipe(18,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(19,"input",28),t.ɵɵtemplate(20,dn,3,3,"mat-error",6),t.ɵɵelementEnd(),t.ɵɵelementStart(21,"mat-form-field",25)(22,"mat-label"),t.ɵɵtext(23),t.ɵɵpipe(24,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(25,"mat-select",29),t.ɵɵtemplate(26,un,3,4,"mat-option",12),t.ɵɵelementEnd(),t.ɵɵtemplate(27,cn,3,3,"mat-error",6),t.ɵɵelementEnd()()()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(4),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(5,9,"tb.rulenode.circle-center-latitude")),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",e.geoActionConfigForm.get("centerLatitude").hasError("required")),t.ɵɵadvance(3),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(11,11,"tb.rulenode.circle-center-longitude")),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",e.geoActionConfigForm.get("centerLongitude").hasError("required")),t.ɵɵadvance(4),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(18,13,"tb.rulenode.range")),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",e.geoActionConfigForm.get("range").hasError("required")),t.ɵɵadvance(3),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(24,15,"tb.rulenode.range-units")),t.ɵɵadvance(3),t.ɵɵproperty("ngForOf",e.rangeUnits),t.ɵɵadvance(),t.ɵɵproperty("ngIf",e.geoActionConfigForm.get("rangeUnit").hasError("required"))}}function gn(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.polygon-definition-required")," "))}function hn(e,n){if(1&e&&(t.ɵɵelementStart(0,"div",9)(1,"mat-form-field",30)(2,"mat-label",31),t.ɵɵtext(3,"tb.rulenode.polygon-definition"),t.ɵɵelementEnd(),t.ɵɵelement(4,"input",32),t.ɵɵelementStart(5,"mat-icon",33),t.ɵɵpipe(6,"translate"),t.ɵɵtext(7," help "),t.ɵɵelementEnd(),t.ɵɵtemplate(8,gn,3,3,"mat-error",6),t.ɵɵelementEnd()()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(5),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(6,2,"tb.rulenode.polygon-definition-hint")),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",e.geoActionConfigForm.get("polygonsDefinition").hasError("required"))}}function yn(e,n){if(1&e&&(t.ɵɵelementStart(0,"tb-toggle-option",22),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=n.$implicit,r=t.ɵɵnextContext();t.ɵɵproperty("value",r.presenceMonitoringStrategies.get(e).value),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,2,r.presenceMonitoringStrategies.get(e).name)," ")}}function bn(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.min-inside-duration-value-required")," "))}function vn(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.time-value-range")," "))}function xn(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.time-value-range")," "))}function Cn(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",22),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=n.$implicit,r=t.ɵɵnextContext(2);t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,2,r.timeUnitsTranslationMap.get(e))," ")}}function Sn(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.min-outside-duration-value-required")," "))}function Tn(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.time-value-range")," "))}function In(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.time-value-range")," "))}function En(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",22),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=n.$implicit,r=t.ɵɵnextContext(2);t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,2,r.timeUnitsTranslationMap.get(e))," ")}}function Fn(e,n){if(1&e&&(t.ɵɵelementStart(0,"div")(1,"div",34)(2,"mat-form-field",35)(3,"mat-label",31),t.ɵɵtext(4,"tb.rulenode.min-inside-duration"),t.ɵɵelementEnd(),t.ɵɵelement(5,"input",36),t.ɵɵtemplate(6,bn,3,3,"mat-error",6)(7,vn,3,3,"mat-error",6)(8,xn,3,3,"mat-error",6),t.ɵɵelementEnd(),t.ɵɵelementStart(9,"mat-form-field",35)(10,"mat-label",31),t.ɵɵtext(11,"tb.rulenode.min-inside-duration-time-unit"),t.ɵɵelementEnd(),t.ɵɵelementStart(12,"mat-select",37),t.ɵɵtemplate(13,Cn,3,4,"mat-option",12),t.ɵɵelementEnd()()(),t.ɵɵelementStart(14,"div",34)(15,"mat-form-field",35)(16,"mat-label",31),t.ɵɵtext(17,"tb.rulenode.min-outside-duration"),t.ɵɵelementEnd(),t.ɵɵelement(18,"input",38),t.ɵɵtemplate(19,Sn,3,3,"mat-error",6)(20,Tn,3,3,"mat-error",6)(21,In,3,3,"mat-error",6),t.ɵɵelementEnd(),t.ɵɵelementStart(22,"mat-form-field",35)(23,"mat-label",31),t.ɵɵtext(24,"tb.rulenode.min-outside-duration-time-unit"),t.ɵɵelementEnd(),t.ɵɵelementStart(25,"mat-select",39),t.ɵɵtemplate(26,En,3,4,"mat-option",12),t.ɵɵelementEnd()()()()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(6),t.ɵɵproperty("ngIf",e.geoActionConfigForm.get("minInsideDuration").hasError("required")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",e.geoActionConfigForm.get("minInsideDuration").hasError("min")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",e.geoActionConfigForm.get("minInsideDuration").hasError("max")),t.ɵɵadvance(5),t.ɵɵproperty("ngForOf",e.timeUnits),t.ɵɵadvance(6),t.ɵɵproperty("ngIf",e.geoActionConfigForm.get("minOutsideDuration").hasError("required")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",e.geoActionConfigForm.get("minOutsideDuration").hasError("min")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",e.geoActionConfigForm.get("minOutsideDuration").hasError("max")),t.ɵɵadvance(5),t.ɵɵproperty("ngForOf",e.timeUnits)}}class qn extends i{constructor(e,t){super(e),this.store=e,this.fb=t,this.perimeterType=ft,this.perimeterTypes=Object.keys(ft),this.perimeterTypeTranslationMap=gt,this.rangeUnits=Object.keys(bt),this.rangeUnitTranslationMap=vt,this.presenceMonitoringStrategies=en,this.presenceMonitoringStrategyKeys=Array.from(this.presenceMonitoringStrategies.keys()),this.timeUnits=Object.keys(ht),this.timeUnitsTranslationMap=yt,this.defaultPaddingEnable=!0}configForm(){return this.geoActionConfigForm}onConfigurationSet(e){this.geoActionConfigForm=this.fb.group({reportPresenceStatusOnEachMessage:[!e||e.reportPresenceStatusOnEachMessage,[N.required]],latitudeKeyName:[e?e.latitudeKeyName:null,[N.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[N.required]],perimeterType:[e?e.perimeterType:null,[N.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterKeyName:[e?e.perimeterKeyName:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]],minInsideDuration:[e?e.minInsideDuration:null,[N.required,N.min(1),N.max(2147483647)]],minInsideDurationTimeUnit:[e?e.minInsideDurationTimeUnit:null,[N.required]],minOutsideDuration:[e?e.minOutsideDuration:null,[N.required,N.min(1),N.max(2147483647)]],minOutsideDurationTimeUnit:[e?e.minOutsideDurationTimeUnit:null,[N.required]]})}validatorTriggers(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]}updateValidators(e){const t=this.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,n=this.geoActionConfigForm.get("perimeterType").value;t?this.geoActionConfigForm.get("perimeterKeyName").setValidators([N.required]):this.geoActionConfigForm.get("perimeterKeyName").setValidators([]),t||n!==ft.CIRCLE?(this.geoActionConfigForm.get("centerLatitude").setValidators([]),this.geoActionConfigForm.get("centerLongitude").setValidators([]),this.geoActionConfigForm.get("range").setValidators([]),this.geoActionConfigForm.get("rangeUnit").setValidators([]),this.defaultPaddingEnable=!0):(this.geoActionConfigForm.get("centerLatitude").setValidators([N.required,N.min(-90),N.max(90)]),this.geoActionConfigForm.get("centerLongitude").setValidators([N.required,N.min(-180),N.max(180)]),this.geoActionConfigForm.get("range").setValidators([N.required,N.min(0)]),this.geoActionConfigForm.get("rangeUnit").setValidators([N.required]),this.defaultPaddingEnable=!1),t||n!==ft.POLYGON?this.geoActionConfigForm.get("polygonsDefinition").setValidators([]):this.geoActionConfigForm.get("polygonsDefinition").setValidators([N.required]),this.geoActionConfigForm.get("perimeterKeyName").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})}static{this.ɵfac=function(e){return new(e||qn)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:qn,selectors:[["tb-action-node-gps-geofencing-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:52,vars:42,consts:[[1,"tb-form-panel","no-border","no-padding",3,"formGroup"],[1,"tb-form-panel","stroked"],["translate","",1,"tb-form-panel-title"],[1,"flex","flex-row","gap-4"],[1,"mat-block","max-w-50%","flex-full"],["matInput","","formControlName","latitudeKeyName","required",""],[4,"ngIf"],["matInput","","formControlName","longitudeKeyName","required",""],["translate","",1,"tb-form-hint","tb-primary-fill"],[1,"flex","flex-col"],["hideRequiredMarker","",1,"mat-block","flex-1"],["formControlName","perimeterType"],[3,"value",4,"ngFor","ngForOf"],[1,"tb-form-row","no-border","no-padding","slide-toggle",3,"tb-hint-tooltip-icon"],["formControlName","fetchPerimeterInfoFromMessageMetadata",1,"mat-slide"],["class","mat-block",4,"ngIf"],["class","flex flex-col",4,"ngIf"],[1,"tb-form-panel","stroked","no-padding-bottom"],[1,"flex","flex-col","items-stretch","justify-between","gt-sm:flex-row","lt-md:gap-4"],[1,"tb-form-panel-title"],["formControlName","reportPresenceStatusOnEachMessage","appearance","fill",1,"fetch-to-data-toggle"],[1,"tb-form-hint","tb-primary-fill"],[3,"value"],[1,"mat-block"],["matInput","","formControlName","perimeterKeyName","required",""],[1,"flex-1"],["type","number","min","-90","max","90","step","0.1","matInput","","formControlName","centerLatitude","required",""],["type","number","min","-180","max","180","step","0.1","matInput","","formControlName","centerLongitude","required",""],["type","number","min","0","step","0.1","matInput","","formControlName","range","required",""],["formControlName","rangeUnit","required",""],["subscriptSizing","dynamic",1,"mat-block"],["translate",""],["matInput","","formControlName","polygonsDefinition","required",""],["matSuffix","","aria-hidden","false","aria-label","help-icon","color","primary",1,"margin-8","cursor-pointer",3,"matTooltip"],[1,"flex","flex-col","gt-sm:flex-row","gt-sm:gap-2"],[1,"mat-block","flex-1"],["type","number","step","1","min","1","max","2147483647","matInput","","formControlName","minInsideDuration","required",""],["formControlName","minInsideDurationTimeUnit","required",""],["type","number","step","1","min","1","max","2147483647","matInput","","formControlName","minOutsideDuration","required",""],["formControlName","minOutsideDurationTimeUnit","required",""]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"section",1)(2,"div",2),t.ɵɵtext(3,"tb.rulenode.coordinate-field-names"),t.ɵɵelementEnd(),t.ɵɵelementStart(4,"section")(5,"div",3)(6,"mat-form-field",4)(7,"mat-label"),t.ɵɵtext(8),t.ɵɵpipe(9,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(10,"input",5),t.ɵɵtemplate(11,rn,3,3,"mat-error",6),t.ɵɵelementEnd(),t.ɵɵelementStart(12,"mat-form-field",4)(13,"mat-label"),t.ɵɵtext(14),t.ɵɵpipe(15,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(16,"input",7),t.ɵɵtemplate(17,an,3,3,"mat-error",6),t.ɵɵelementEnd()(),t.ɵɵelementStart(18,"div",8),t.ɵɵtext(19,"tb.rulenode.coordinate-field-hint"),t.ɵɵelementEnd()()(),t.ɵɵelementStart(20,"section",1)(21,"div",2),t.ɵɵtext(22,"tb.rulenode.geofence-configuration"),t.ɵɵelementEnd(),t.ɵɵelementStart(23,"section",9)(24,"mat-form-field",10)(25,"mat-label"),t.ɵɵtext(26),t.ɵɵpipe(27,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(28,"mat-select",11),t.ɵɵtemplate(29,on,3,4,"mat-option",12),t.ɵɵelementEnd()(),t.ɵɵelementStart(30,"div",13),t.ɵɵpipe(31,"translate"),t.ɵɵpipe(32,"translate"),t.ɵɵelementStart(33,"mat-slide-toggle",14),t.ɵɵtext(34),t.ɵɵpipe(35,"translate"),t.ɵɵelementEnd()(),t.ɵɵtemplate(36,sn,9,7,"mat-form-field",15)(37,fn,28,17,"div",16)(38,hn,9,4,"div",16),t.ɵɵelementEnd()(),t.ɵɵelementStart(39,"section",17)(40,"div",18)(41,"div",19),t.ɵɵtext(42),t.ɵɵpipe(43,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(44,"tb-toggle-select",20),t.ɵɵtemplate(45,yn,3,4,"tb-toggle-option",12),t.ɵɵelementEnd()(),t.ɵɵelementStart(46,"div",21),t.ɵɵtext(47),t.ɵɵpipe(48,"translate"),t.ɵɵpipe(49,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(50,"section",9),t.ɵɵtemplate(51,Fn,27,8,"div",6),t.ɵɵelementEnd()()()),2&e&&(t.ɵɵproperty("formGroup",n.geoActionConfigForm),t.ɵɵadvance(8),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(9,18,"tb.rulenode.latitude-field-name")),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",n.geoActionConfigForm.get("latitudeKeyName").hasError("required")),t.ɵɵadvance(3),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(15,20,"tb.rulenode.longitude-field-name")),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",n.geoActionConfigForm.get("longitudeKeyName").hasError("required")),t.ɵɵadvance(3),t.ɵɵclassProp("no-padding-bottom",!n.defaultPaddingEnable),t.ɵɵadvance(6),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(27,22,"tb.rulenode.perimeter-type")),t.ɵɵadvance(3),t.ɵɵproperty("ngForOf",n.perimeterTypes),t.ɵɵadvance(),t.ɵɵpropertyInterpolate("tb-hint-tooltip-icon",n.geoActionConfigForm.get("perimeterType").value===n.perimeterType.CIRCLE?t.ɵɵpipeBind2(31,24,"tb.rulenode.fetch-circle-parameter-info-from-metadata-hint",t.ɵɵpureFunction1(38,nn,n.geoActionConfigForm.get("perimeterKeyName").valid?n.geoActionConfigForm.get("perimeterKeyName").value:"ss_perimeter")):t.ɵɵpipeBind2(32,27,"tb.rulenode.fetch-poligon-parameter-info-from-metadata-hint",t.ɵɵpureFunction1(40,nn,n.geoActionConfigForm.get("perimeterKeyName").valid?n.geoActionConfigForm.get("perimeterKeyName").value:"ss_perimeter"))),t.ɵɵadvance(4),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(35,30,"tb.rulenode.fetch-perimeter-info-from-metadata")," "),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",n.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.geoActionConfigForm.get("perimeterType").value===n.perimeterType.CIRCLE&&!n.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.geoActionConfigForm.get("perimeterType").value===n.perimeterType.POLYGON&&!n.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value),t.ɵɵadvance(4),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(43,32,"tb.rulenode.presence-monitoring-strategy")),t.ɵɵadvance(3),t.ɵɵproperty("ngForOf",n.presenceMonitoringStrategyKeys),t.ɵɵadvance(2),t.ɵɵtextInterpolate1(" ",!1===n.geoActionConfigForm.get("reportPresenceStatusOnEachMessage").value?t.ɵɵpipeBind1(48,34,"tb.rulenode.presence-monitoring-strategy-on-first-message-hint"):t.ɵɵpipeBind1(49,36,"tb.rulenode.presence-monitoring-strategy-on-each-message-hint")," "),t.ɵɵadvance(4),t.ɵɵproperty("ngIf",!1===n.geoActionConfigForm.get("reportPresenceStatusOnEachMessage").value))},dependencies:t.ɵɵgetComponentDepsFactory(qn),styles:["[_nghost-%COMP%] .slide-toggle[_ngcontent-%COMP%]{margin-bottom:18px}"]})}}e("GpsGeoActionConfigComponent",qn);const An=["jsFuncComponent"],kn=["tbelFuncComponent"],Nn=()=>["msg","metadata","msgType"];function wn(e,n){1&e&&t.ɵɵelement(0,"tb-script-lang",8)}function Mn(e,n){if(1&e){const e=t.ɵɵgetCurrentView();t.ɵɵelementStart(0,"tb-js-func",9,0)(2,"button",10),t.ɵɵpipe(3,"translate"),t.ɵɵlistener("click",(function(){t.ɵɵrestoreView(e);const n=t.ɵɵnextContext();return t.ɵɵresetView(n.testScript())})),t.ɵɵelementStart(4,"mat-icon",11),t.ɵɵtext(5,"bug_report"),t.ɵɵelementEnd()()()}if(2&e){const e=t.ɵɵnextContext();t.ɵɵproperty("functionArgs",t.ɵɵpureFunction0(4,Nn)),t.ɵɵadvance(2),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(3,2,e.testScriptLabel))}}function Bn(e,n){if(1&e){const e=t.ɵɵgetCurrentView();t.ɵɵelementStart(0,"tb-js-func",12,1)(2,"button",10),t.ɵɵpipe(3,"translate"),t.ɵɵlistener("click",(function(){t.ɵɵrestoreView(e);const n=t.ɵɵnextContext();return t.ɵɵresetView(n.testScript())})),t.ɵɵelementStart(4,"mat-icon",11),t.ɵɵtext(5,"bug_report"),t.ɵɵelementEnd()()()}if(2&e){const e=t.ɵɵnextContext();t.ɵɵproperty("functionArgs",t.ɵɵpureFunction0(6,Nn))("disableUndefinedCheck",!0)("scriptLanguage",e.scriptLanguage.TBEL),t.ɵɵadvance(2),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(3,4,e.testScriptLabel))}}class Vn extends i{constructor(e,t,r,a){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=r,this.translate=a,this.tbelEnabled=D(this.store).tbelEnabled,this.scriptLanguage=s,this.changeScript=new n,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-to-string-function"}configForm(){return this.logConfigForm}onConfigurationSet(e){this.logConfigForm=this.fb.group({scriptLang:[e?e.scriptLang:s.JS,[N.required]],jsScript:[e?e.jsScript:null,[]],tbelScript:[e?e.tbelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.logConfigForm.get("scriptLang").value;t!==s.TBEL||this.tbelEnabled||(t=s.JS,this.logConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.logConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.logConfigForm.get("jsScript").setValidators(t===s.JS?[N.required]:[]),this.logConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.logConfigForm.get("tbelScript").setValidators(t===s.TBEL?[N.required]:[]),this.logConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=s.JS)),e}testScript(e){const t=this.logConfigForm.get("scriptLang").value,n=t===s.JS?"jsScript":"tbelScript",r=t===s.JS?"rulenode/log_node_script_fn":"rulenode/tbel/log_node_script_fn",a=this.logConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(a,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.logConfigForm.get(n).setValue(e),this.changeScript.emit())}))}onValidate(){this.logConfigForm.get("scriptLang").value===s.JS&&this.jsFuncComponent.validateOnSubmit()}static{this.ɵfac=function(e){return new(e||Vn)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder),t.ɵɵdirectiveInject(L.NodeScriptTestService),t.ɵɵdirectiveInject(K.TranslateService))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Vn,selectors:[["tb-action-node-log-config"]],viewQuery:function(e,n){if(1&e&&(t.ɵɵviewQuery(An,5),t.ɵɵviewQuery(kn,5)),2&e){let e;t.ɵɵqueryRefresh(e=t.ɵɵloadQuery())&&(n.jsFuncComponent=e.first),t.ɵɵqueryRefresh(e=t.ɵɵloadQuery())&&(n.tbelFuncComponent=e.first)}},features:[t.ɵɵInheritDefinitionFeature],decls:8,vars:7,consts:[["jsFuncComponent",""],["tbelFuncComponent",""],[1,"flex","flex-col",3,"formGroup"],["formControlName","scriptLang",4,"ngIf"],["formControlName","jsScript","functionName","ToString","helpId","rulenode/log_node_script_fn","noValidate","true",3,"functionArgs",4,"ngIf"],["formControlName","tbelScript","functionName","ToString","helpId","rulenode/tbel/log_node_script_fn","noValidate","true",3,"functionArgs","disableUndefinedCheck","scriptLanguage",4,"ngIf"],[1,"flex","flex-row"],["mat-button","","mat-raised-button","","color","primary",3,"click"],["formControlName","scriptLang"],["formControlName","jsScript","functionName","ToString","helpId","rulenode/log_node_script_fn","noValidate","true",3,"functionArgs"],["toolbarSuffixButton","","mat-icon-button","","matTooltipPosition","above",1,"tb-mat-32",3,"click","matTooltip"],["color","primary",1,"material-icons"],["formControlName","tbelScript","functionName","ToString","helpId","rulenode/tbel/log_node_script_fn","noValidate","true",3,"functionArgs","disableUndefinedCheck","scriptLanguage"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",2),t.ɵɵtemplate(1,wn,1,0,"tb-script-lang",3)(2,Mn,6,5,"tb-js-func",4)(3,Bn,6,7,"tb-js-func",5),t.ɵɵelementStart(4,"div",6)(5,"button",7),t.ɵɵlistener("click",(function(){return n.testScript()})),t.ɵɵtext(6),t.ɵɵpipe(7,"translate"),t.ɵɵelementEnd()()()),2&e&&(t.ɵɵproperty("formGroup",n.logConfigForm),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.tbelEnabled),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.logConfigForm.get("scriptLang").value===n.scriptLanguage.JS),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.logConfigForm.get("scriptLang").value===n.scriptLanguage.TBEL),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(7,5,n.testScriptLabel)," "))},dependencies:t.ɵɵgetComponentDepsFactory(Vn),encapsulation:2})}}function On(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.interval-seconds-required")," "))}function Dn(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.min-interval-seconds-message")," "))}function Ln(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.output-timeseries-key-prefix-required")," "))}e("LogConfigComponent",Vn);class Pn extends i{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.msgCountConfigForm}onConfigurationSet(e){this.msgCountConfigForm=this.fb.group({interval:[e?e.interval:null,[N.required,N.min(1)]],telemetryPrefix:[e?e.telemetryPrefix:null,[N.required]]})}static{this.ɵfac=function(e){return new(e||Pn)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Pn,selectors:[["tb-action-node-msg-count-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:12,vars:4,consts:[[1,"flex","flex-col",3,"formGroup"],[1,"mat-block"],["translate",""],["required","","type","number","min","1","step","1","matInput","","formControlName","interval"],[4,"ngIf"],["required","","matInput","","formControlName","telemetryPrefix"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"mat-form-field",1)(2,"mat-label",2),t.ɵɵtext(3,"tb.rulenode.interval-seconds"),t.ɵɵelementEnd(),t.ɵɵelement(4,"input",3),t.ɵɵtemplate(5,On,3,3,"mat-error",4)(6,Dn,3,3,"mat-error",4),t.ɵɵelementEnd(),t.ɵɵelementStart(7,"mat-form-field",1)(8,"mat-label",2),t.ɵɵtext(9,"tb.rulenode.output-timeseries-key-prefix"),t.ɵɵelementEnd(),t.ɵɵelement(10,"input",5),t.ɵɵtemplate(11,Ln,3,3,"mat-error",4),t.ɵɵelementEnd()()),2&e&&(t.ɵɵproperty("formGroup",n.msgCountConfigForm),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",n.msgCountConfigForm.get("interval").hasError("required")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.msgCountConfigForm.get("interval").hasError("min")),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",n.msgCountConfigForm.get("telemetryPrefix").hasError("required")))},dependencies:t.ɵɵgetComponentDepsFactory(Pn),encapsulation:2})}}function Rn(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.period-seconds-required")," "))}function _n(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.min-period-0-seconds-message")," "))}function jn(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-form-field",5)(1,"mat-label",6),t.ɵɵtext(2,"tb.rulenode.period-seconds"),t.ɵɵelementEnd(),t.ɵɵelement(3,"input",9),t.ɵɵtemplate(4,Rn,3,3,"mat-error",8)(5,_n,3,3,"mat-error",8),t.ɵɵelementEnd()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(4),t.ɵɵproperty("ngIf",e.msgDelayConfigForm.get("periodInSeconds").hasError("required")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",e.msgDelayConfigForm.get("periodInSeconds").hasError("min"))}}function Gn(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.period-in-seconds-pattern-required")," "))}function Kn(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-form-field",10)(1,"mat-label",6),t.ɵɵtext(2,"tb.rulenode.period-in-seconds-pattern"),t.ɵɵelementEnd(),t.ɵɵelement(3,"input",11),t.ɵɵtemplate(4,Gn,3,3,"mat-error",8),t.ɵɵelementStart(5,"mat-hint",6),t.ɵɵtext(6,"tb.rulenode.general-pattern-hint"),t.ɵɵelementEnd()()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(4),t.ɵɵproperty("ngIf",e.msgDelayConfigForm.get("periodInSecondsPattern").hasError("required"))}}function Un(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.max-pending-messages-required")," "))}function Hn(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.max-pending-messages-range")," "))}function zn(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.max-pending-messages-range")," "))}e("MsgCountConfigComponent",Pn);class $n extends i{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.msgDelayConfigForm}onConfigurationSet(e){this.msgDelayConfigForm=this.fb.group({useMetadataPeriodInSecondsPatterns:[!!e&&e.useMetadataPeriodInSecondsPatterns,[]],periodInSeconds:[e?e.periodInSeconds:null,[]],periodInSecondsPattern:[e?e.periodInSecondsPattern:null,[]],maxPendingMsgs:[e?e.maxPendingMsgs:null,[N.required,N.min(1),N.max(1e5)]]})}validatorTriggers(){return["useMetadataPeriodInSecondsPatterns"]}updateValidators(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([N.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([N.required,N.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})}static{this.ɵfac=function(e){return new(e||$n)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:$n,selectors:[["tb-action-node-msg-delay-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:16,vars:9,consts:[["periodInSecondsPattern",""],[1,"flex","flex-col",3,"formGroup"],["formControlName","useMetadataPeriodInSecondsPatterns"],["translate","",1,"tb-hint"],["class","mat-block",4,"ngIf","ngIfElse"],[1,"mat-block"],["translate",""],["required","","type","number","min","1","max","100000","step","1","matInput","","formControlName","maxPendingMsgs"],[4,"ngIf"],["required","","type","number","min","0","step","1","matInput","","formControlName","periodInSeconds"],["subscriptSizing","dynamic",1,"mat-block"],["required","","matInput","","formControlName","periodInSecondsPattern"]],template:function(e,n){if(1&e&&(t.ɵɵelementStart(0,"section",1)(1,"mat-checkbox",2),t.ɵɵtext(2),t.ɵɵpipe(3,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(4,"div",3),t.ɵɵtext(5,"tb.rulenode.use-metadata-period-in-seconds-patterns-hint"),t.ɵɵelementEnd(),t.ɵɵtemplate(6,jn,6,2,"mat-form-field",4)(7,Kn,7,1,"ng-template",null,0,t.ɵɵtemplateRefExtractor),t.ɵɵelementStart(9,"mat-form-field",5)(10,"mat-label",6),t.ɵɵtext(11,"tb.rulenode.max-pending-messages"),t.ɵɵelementEnd(),t.ɵɵelement(12,"input",7),t.ɵɵtemplate(13,Un,3,3,"mat-error",8)(14,Hn,3,3,"mat-error",8)(15,zn,3,3,"mat-error",8),t.ɵɵelementEnd()()),2&e){const e=t.ɵɵreference(8);t.ɵɵproperty("formGroup",n.msgDelayConfigForm),t.ɵɵadvance(2),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(3,7,"tb.rulenode.use-metadata-period-in-seconds-patterns")," "),t.ɵɵadvance(4),t.ɵɵproperty("ngIf",!0!==n.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value)("ngIfElse",e),t.ɵɵadvance(7),t.ɵɵproperty("ngIf",n.msgDelayConfigForm.get("maxPendingMsgs").hasError("required")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.msgDelayConfigForm.get("maxPendingMsgs").hasError("min")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.msgDelayConfigForm.get("maxPendingMsgs").hasError("max"))}},dependencies:t.ɵɵgetComponentDepsFactory($n),encapsulation:2})}}e("MsgDelayConfigComponent",$n);const Qn=()=>({standalone:!0});function Jn(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",10),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=n.$implicit,r=t.ɵɵnextContext();t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,2,r.telemetryTypeTranslationsMap.get(e))," ")}}class Yn extends i{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopes=Object.keys(o),this.telemetryTypeTranslationsMap=l}configForm(){return this.pushToCloudConfigForm}onConfigurationSet(e){this.pushToCloudConfigForm=this.fb.group({scope:[e?e.scope:null,[N.required]]})}static{this.ɵfac=function(e){return new(e||Yn)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Yn,selectors:[["tb-action-node-push-to-cloud-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:19,vars:16,consts:[[1,"tb-form-panel","no-border","no-padding",3,"formGroup"],[1,"tb-form-panel","stroked"],[3,"hintText"],[1,"tb-form-row","no-border","no-padding","tb-standard-fields"],[1,"flex"],["required","","matInput","","formControlName","scope",1,"tb-entity-type-select"],[3,"value",4,"ngFor","ngForOf"],["type","text","matInput","","readonly","","disabled","",3,"ngModel","ngModelOptions"],["type","button","matSuffix","","mat-icon-button","","aria-label","Copy","ngxClipboard","",3,"cbContent","matTooltip"],["aria-hidden","false","aria-label","help-icon"],[3,"value"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"div",1),t.ɵɵelement(2,"tb-example-hint",2),t.ɵɵelementStart(3,"div",3)(4,"mat-form-field",4)(5,"mat-label"),t.ɵɵtext(6),t.ɵɵpipe(7,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(8,"mat-select",5),t.ɵɵtemplate(9,Jn,3,4,"mat-option",6),t.ɵɵelementEnd()(),t.ɵɵelementStart(10,"mat-form-field",4)(11,"mat-label"),t.ɵɵtext(12),t.ɵɵpipe(13,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(14,"input",7),t.ɵɵelementStart(15,"button",8),t.ɵɵpipe(16,"translate"),t.ɵɵelementStart(17,"mat-icon",9),t.ɵɵtext(18,"content_copy "),t.ɵɵelementEnd()()()()()()),2&e&&(t.ɵɵproperty("formGroup",n.pushToCloudConfigForm),t.ɵɵadvance(2),t.ɵɵproperty("hintText","tb.rulenode.attributes-scope-hint"),t.ɵɵadvance(4),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(7,9,"tb.rulenode.attributes-scope")),t.ɵɵadvance(3),t.ɵɵproperty("ngForOf",n.attributeScopes),t.ɵɵadvance(3),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(13,11,"tb.rulenode.attributes-scope-value")),t.ɵɵadvance(2),t.ɵɵproperty("ngModel",n.pushToCloudConfigForm.get("scope").value)("ngModelOptions",t.ɵɵpureFunction0(15,Qn)),t.ɵɵadvance(),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(16,13,"tb.rulenode.attributes-scope-value-copy")),t.ɵɵproperty("cbContent",n.pushToCloudConfigForm.get("scope").value))},dependencies:t.ɵɵgetComponentDepsFactory(Yn),encapsulation:2})}}e("PushToCloudConfigComponent",Yn);const Wn=()=>({standalone:!0});function Xn(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",10),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=n.$implicit,r=t.ɵɵnextContext();t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,2,r.telemetryTypeTranslationsMap.get(e))," ")}}class Zn extends i{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopes=Object.keys(o),this.telemetryTypeTranslationsMap=l}configForm(){return this.pushToEdgeConfigForm}onConfigurationSet(e){this.pushToEdgeConfigForm=this.fb.group({scope:[e?e.scope:null,[N.required]]})}static{this.ɵfac=function(e){return new(e||Zn)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Zn,selectors:[["tb-action-node-push-to-edge-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:19,vars:16,consts:[[1,"tb-form-panel","no-border","no-padding",3,"formGroup"],[1,"tb-form-panel","stroked"],[3,"hintText"],[1,"tb-form-row","no-border","no-padding","tb-standard-fields"],[1,"flex"],["required","","matInput","","formControlName","scope",1,"tb-entity-type-select"],[3,"value",4,"ngFor","ngForOf"],["type","text","matInput","","readonly","","disabled","",3,"ngModel","ngModelOptions"],["type","button","matSuffix","","mat-icon-button","","aria-label","Copy","ngxClipboard","",3,"cbContent","matTooltip"],["aria-hidden","false","aria-label","help-icon"],[3,"value"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"div",1),t.ɵɵelement(2,"tb-example-hint",2),t.ɵɵelementStart(3,"div",3)(4,"mat-form-field",4)(5,"mat-label"),t.ɵɵtext(6),t.ɵɵpipe(7,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(8,"mat-select",5),t.ɵɵtemplate(9,Xn,3,4,"mat-option",6),t.ɵɵelementEnd()(),t.ɵɵelementStart(10,"mat-form-field",4)(11,"mat-label"),t.ɵɵtext(12),t.ɵɵpipe(13,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(14,"input",7),t.ɵɵelementStart(15,"button",8),t.ɵɵpipe(16,"translate"),t.ɵɵelementStart(17,"mat-icon",9),t.ɵɵtext(18,"content_copy "),t.ɵɵelementEnd()()()()()()),2&e&&(t.ɵɵproperty("formGroup",n.pushToEdgeConfigForm),t.ɵɵadvance(2),t.ɵɵproperty("hintText","tb.rulenode.attributes-scope-hint"),t.ɵɵadvance(4),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(7,9,"tb.rulenode.attributes-scope")),t.ɵɵadvance(3),t.ɵɵproperty("ngForOf",n.attributeScopes),t.ɵɵadvance(3),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(13,11,"tb.rulenode.attributes-scope-value")),t.ɵɵadvance(2),t.ɵɵproperty("ngModel",n.pushToEdgeConfigForm.get("scope").value)("ngModelOptions",t.ɵɵpureFunction0(15,Wn)),t.ɵɵadvance(),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(16,13,"tb.rulenode.attributes-scope-value-copy")),t.ɵɵproperty("cbContent",n.pushToEdgeConfigForm.get("scope").value))},dependencies:t.ɵɵgetComponentDepsFactory(Zn),encapsulation:2})}}e("PushToEdgeConfigComponent",Zn);class er extends i{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.rpcReplyConfigForm}onConfigurationSet(e){this.rpcReplyConfigForm=this.fb.group({serviceIdMetaDataAttribute:[e?e.serviceIdMetaDataAttribute:null,[]],sessionIdMetaDataAttribute:[e?e.sessionIdMetaDataAttribute:null,[]],requestIdMetaDataAttribute:[e?e.requestIdMetaDataAttribute:null,[]]})}static{this.ɵfac=function(e){return new(e||er)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:er,selectors:[["tb-action-node-rpc-reply-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:17,vars:2,consts:[[1,"tb-form-panel","stroked","no-padding-bottom",3,"formGroup"],["translate","",1,"tb-form-panel-title"],[3,"hintText"],[1,"tb-form-row","no-border","no-padding","tb-standard-fields","column-xs"],[1,"flex"],["translate",""],["matInput","","formControlName","serviceIdMetaDataAttribute"],["matInput","","formControlName","sessionIdMetaDataAttribute"],["matInput","","formControlName","requestIdMetaDataAttribute"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"div",1),t.ɵɵtext(2,"tb.rulenode.reply-routing-configuration"),t.ɵɵelementEnd(),t.ɵɵelement(3,"tb-example-hint",2),t.ɵɵelementStart(4,"div",3)(5,"mat-form-field",4)(6,"mat-label",5),t.ɵɵtext(7,"tb.rulenode.service-id-metadata-attribute"),t.ɵɵelementEnd(),t.ɵɵelement(8,"input",6),t.ɵɵelementEnd(),t.ɵɵelementStart(9,"mat-form-field",4)(10,"mat-label",5),t.ɵɵtext(11,"tb.rulenode.session-id-metadata-attribute"),t.ɵɵelementEnd(),t.ɵɵelement(12,"input",7),t.ɵɵelementEnd(),t.ɵɵelementStart(13,"mat-form-field",4)(14,"mat-label",5),t.ɵɵtext(15,"tb.rulenode.request-id-metadata-attribute"),t.ɵɵelementEnd(),t.ɵɵelement(16,"input",8),t.ɵɵelementEnd()()()),2&e&&(t.ɵɵproperty("formGroup",n.rpcReplyConfigForm),t.ɵɵadvance(3),t.ɵɵproperty("hintText","tb.rulenode.rpc-reply-routing-configuration-hint"))},dependencies:t.ɵɵgetComponentDepsFactory(er),encapsulation:2})}}function tr(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.timeout-required")," "))}function nr(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.min-timeout-message")," "))}e("RpcReplyConfigComponent",er);class rr extends i{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.rpcRequestConfigForm}onConfigurationSet(e){this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[e?e.timeoutInSeconds:null,[N.required,N.min(0)]]})}static{this.ɵfac=function(e){return new(e||rr)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:rr,selectors:[["tb-action-node-rpc-request-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:7,vars:3,consts:[[1,"flex","flex-col",3,"formGroup"],[1,"mat-block","flex-1"],["translate",""],["type","number","min","0","step","1","matInput","","formControlName","timeoutInSeconds","required",""],[4,"ngIf"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"mat-form-field",1)(2,"mat-label",2),t.ɵɵtext(3,"tb.rulenode.timeout-sec"),t.ɵɵelementEnd(),t.ɵɵelement(4,"input",3),t.ɵɵtemplate(5,tr,3,3,"mat-error",4)(6,nr,3,3,"mat-error",4),t.ɵɵelementEnd()()),2&e&&(t.ɵɵproperty("formGroup",n.rpcRequestConfigForm),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",n.rpcRequestConfigForm.get("timeoutInSeconds").hasError("required")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.rpcRequestConfigForm.get("timeoutInSeconds").hasError("min")))},dependencies:t.ɵɵgetComponentDepsFactory(rr),encapsulation:2})}}function ar(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.custom-table-name-required")," "))}function ir(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.min-default-ttl-message")," "))}function or(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.default-ttl-required")," "))}e("RpcRequestConfigComponent",rr);class lr extends i{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.saveToCustomTableConfigForm}onConfigurationSet(e){this.saveToCustomTableConfigForm=this.fb.group({tableName:[e?e.tableName:null,[N.required,N.pattern(/.*\S.*/)]],fieldsMapping:[e?e.fieldsMapping:null,[N.required]],defaultTtl:[e?e.defaultTtl:0,[N.required,N.min(0)]]})}prepareOutputConfig(e){return e.tableName=e.tableName.trim(),e}static{this.ɵfac=function(e){return new(e||lr)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:lr,selectors:[["tb-action-node-custom-table-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:24,vars:26,consts:[[1,"tb-form-panel","no-border","no-padding",3,"formGroup"],["subscriptSizing","dynamic",1,"mat-block"],["translate",""],["required","","matInput","","formControlName","tableName"],["aria-hidden","false","aria-label","help-icon","matSuffix","",1,"help-icon","margin-8","cursor-pointer",3,"matTooltip"],[4,"ngIf"],["required","","formControlName","fieldsMapping",3,"labelText","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText"],[1,"mat-block","flex-1"],["type","number","min","0","step","1","matInput","","formControlName","defaultTtl","required",""]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"mat-form-field",1)(2,"mat-label",2),t.ɵɵtext(3,"tb.rulenode.custom-table-name"),t.ɵɵelementEnd(),t.ɵɵelement(4,"input",3),t.ɵɵelementStart(5,"mat-icon",4),t.ɵɵpipe(6,"translate"),t.ɵɵtext(7," help "),t.ɵɵelementEnd(),t.ɵɵtemplate(8,ar,3,3,"mat-error",5),t.ɵɵelementEnd(),t.ɵɵelement(9,"tb-kv-map-config",6),t.ɵɵpipe(10,"translate"),t.ɵɵpipe(11,"translate"),t.ɵɵpipe(12,"translate"),t.ɵɵpipe(13,"translate"),t.ɵɵpipe(14,"translate"),t.ɵɵpipe(15,"translate"),t.ɵɵelementStart(16,"mat-form-field",7)(17,"mat-label",2),t.ɵɵtext(18,"tb.rulenode.default-ttl"),t.ɵɵelementEnd(),t.ɵɵelement(19,"input",8),t.ɵɵelementStart(20,"mat-hint",2),t.ɵɵtext(21,"tb.rulenode.default-ttl-zero-hint"),t.ɵɵelementEnd(),t.ɵɵtemplate(22,ir,3,3,"mat-error",5)(23,or,3,3,"mat-error",5),t.ɵɵelementEnd()()),2&e&&(t.ɵɵproperty("formGroup",n.saveToCustomTableConfigForm),t.ɵɵadvance(5),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(6,12,"tb.rulenode.custom-table-hint")),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",n.saveToCustomTableConfigForm.get("tableName").hasError("required")||n.saveToCustomTableConfigForm.get("tableName").hasError("pattern")),t.ɵɵadvance(),t.ɵɵproperty("labelText",t.ɵɵpipeBind1(10,14,"tb.rulenode.fields-mapping"))("requiredText",t.ɵɵpipeBind1(11,16,"tb.rulenode.fields-mapping-required"))("keyText",t.ɵɵpipeBind1(12,18,"tb.rulenode.message-field"))("keyRequiredText",t.ɵɵpipeBind1(13,20,"tb.rulenode.message-field-required"))("valText",t.ɵɵpipeBind1(14,22,"tb.rulenode.table-col"))("valRequiredText",t.ɵɵpipeBind1(15,24,"tb.rulenode.table-col-required"))("hintText","tb.rulenode.fields-mapping-hint"),t.ɵɵadvance(13),t.ɵɵproperty("ngIf",n.saveToCustomTableConfigForm.get("defaultTtl").hasError("min")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.saveToCustomTableConfigForm.get("defaultTtl").hasError("required")))},dependencies:t.ɵɵgetComponentDepsFactory(lr),encapsulation:2})}}function sr(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.default-ttl-required")," "))}function pr(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.min-default-ttl-message")," "))}e("SaveToCustomTableConfigComponent",lr);class mr extends i{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.timeseriesConfigForm}onConfigurationSet(e){this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[N.required,N.min(0)]],skipLatestPersistence:[!!e&&e.skipLatestPersistence,[]],useServerTs:[!!e&&e.useServerTs,[]]})}static{this.ɵfac=function(e){return new(e||mr)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:mr,selectors:[["tb-action-node-timeseries-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:21,vars:18,consts:[[1,"tb-form-panel","no-border","no-padding",3,"formGroup"],[1,"mat-block","flex-1"],["translate",""],["type","number","min","0","step","1","matInput","","formControlName","defaultTTL","required",""],["aria-hidden","false","aria-label","help-icon","matSuffix","",1,"help-icon","margin-8","cursor-pointer",3,"matTooltip"],[4,"ngIf"],[1,"tb-form-panel","stroked"],[1,"tb-form-row","no-border","no-padding",3,"tb-hint-tooltip-icon"],["formControlName","useServerTs",1,"mat-slide"],["formControlName","skipLatestPersistence",1,"mat-slide"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"mat-form-field",1)(2,"mat-label",2),t.ɵɵtext(3,"tb.rulenode.default-ttl"),t.ɵɵelementEnd(),t.ɵɵelement(4,"input",3),t.ɵɵelementStart(5,"mat-icon",4),t.ɵɵpipe(6,"translate"),t.ɵɵtext(7," help "),t.ɵɵelementEnd(),t.ɵɵtemplate(8,sr,3,3,"mat-error",5)(9,pr,3,3,"mat-error",5),t.ɵɵelementEnd(),t.ɵɵelementStart(10,"div",6)(11,"div",7),t.ɵɵpipe(12,"translate"),t.ɵɵelementStart(13,"mat-slide-toggle",8),t.ɵɵtext(14),t.ɵɵpipe(15,"translate"),t.ɵɵelementEnd()(),t.ɵɵelementStart(16,"div",7),t.ɵɵpipe(17,"translate"),t.ɵɵelementStart(18,"mat-slide-toggle",9),t.ɵɵtext(19),t.ɵɵpipe(20,"translate"),t.ɵɵelementEnd()()()()),2&e&&(t.ɵɵproperty("formGroup",n.timeseriesConfigForm),t.ɵɵadvance(5),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(6,8,"tb.rulenode.default-ttl-hint")),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",n.timeseriesConfigForm.get("defaultTTL").hasError("required")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.timeseriesConfigForm.get("defaultTTL").hasError("min")),t.ɵɵadvance(2),t.ɵɵpropertyInterpolate("tb-hint-tooltip-icon",t.ɵɵpipeBind1(12,10,"tb.rulenode.use-server-ts-hint")),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(15,12,"tb.rulenode.use-server-ts")," "),t.ɵɵadvance(2),t.ɵɵpropertyInterpolate("tb-hint-tooltip-icon",t.ɵɵpipeBind1(17,14,"tb.rulenode.skip-latest-persistence-hint")),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(20,16,"tb.rulenode.skip-latest-persistence")," "))},dependencies:t.ɵɵgetComponentDepsFactory(mr),encapsulation:2})}}function dr(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.customer-name-pattern-required")," "))}function ur(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-form-field",6)(1,"mat-label",7),t.ɵɵtext(2,"tb.rulenode.customer-name-pattern"),t.ɵɵelementEnd(),t.ɵɵelement(3,"input",8),t.ɵɵtemplate(4,dr,3,3,"mat-error",9),t.ɵɵelementStart(5,"mat-hint",7),t.ɵɵtext(6,"tb.rulenode.customer-name-pattern-hint"),t.ɵɵelementEnd()()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(4),t.ɵɵproperty("ngIf",e.unassignCustomerConfigForm.get("customerNamePattern").hasError("required")||e.unassignCustomerConfigForm.get("customerNamePattern").hasError("pattern"))}}e("TimeseriesConfigComponent",mr);class cr extends i{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.unassignCustomerConfigForm}prepareInputConfig(e){return{customerNamePattern:P(e?.customerNamePattern)?e.customerNamePattern:null,unassignFromCustomer:P(e?.customerNamePattern)}}onConfigurationSet(e){this.unassignCustomerConfigForm=this.fb.group({customerNamePattern:[e.customerNamePattern,[]],unassignFromCustomer:[e.unassignFromCustomer,[]]})}validatorTriggers(){return["unassignFromCustomer"]}updateValidators(e){this.unassignCustomerConfigForm.get("unassignFromCustomer").value?this.unassignCustomerConfigForm.get("customerNamePattern").setValidators([N.required,N.pattern(/.*\S.*/)]):this.unassignCustomerConfigForm.get("customerNamePattern").setValidators([]),this.unassignCustomerConfigForm.get("customerNamePattern").updateValueAndValidity({emitEvent:e})}prepareOutputConfig(e){return{customerNamePattern:e.unassignFromCustomer?e.customerNamePattern.trim():null}}static{this.ɵfac=function(e){return new(e||cr)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:cr,selectors:[["tb-action-node-un-assign-to-customer-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:9,vars:10,consts:[[1,"flex","flex-col",3,"formGroup"],[1,"tb-form-panel","no-padding","no-border"],[1,"tb-form-panel","stroked"],[1,"tb-form-row","no-border","no-padding",3,"tb-hint-tooltip-icon"],["formControlName","unassignFromCustomer",1,"mat-slide"],["class","mat-block","subscriptSizing","dynamic",4,"ngIf"],["subscriptSizing","dynamic",1,"mat-block"],["translate",""],["required","","matInput","","formControlName","customerNamePattern"],[4,"ngIf"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"div",1)(2,"div",2)(3,"div",3),t.ɵɵpipe(4,"translate"),t.ɵɵelementStart(5,"mat-slide-toggle",4),t.ɵɵtext(6),t.ɵɵpipe(7,"translate"),t.ɵɵelementEnd()(),t.ɵɵtemplate(8,ur,7,1,"mat-form-field",5),t.ɵɵelementEnd()()()),2&e&&(t.ɵɵproperty("formGroup",n.unassignCustomerConfigForm),t.ɵɵadvance(2),t.ɵɵclassProp("no-padding-bottom",n.unassignCustomerConfigForm.get("unassignFromCustomer").value),t.ɵɵadvance(),t.ɵɵpropertyInterpolate("tb-hint-tooltip-icon",t.ɵɵpipeBind1(4,6,"tb.rulenode.unassign-from-customer-tooltip")),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(7,8,"tb.rulenode.unassign-from-customer")," "),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",n.unassignCustomerConfigForm.get("unassignFromCustomer").value))},dependencies:t.ɵɵgetComponentDepsFactory(cr),encapsulation:2})}}e("UnassignCustomerConfigComponent",cr);class fr extends i{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.sendRestApiCallReplyConfigForm}onConfigurationSet(e){this.sendRestApiCallReplyConfigForm=this.fb.group({requestIdMetaDataAttribute:[e?e.requestIdMetaDataAttribute:null,[]],serviceIdMetaDataAttribute:[e?e.serviceIdMetaDataAttribute:null,[]]})}static{this.ɵfac=function(e){return new(e||fr)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:fr,selectors:[["tb-action-node-send-rest-api-call-reply-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:13,vars:2,consts:[[1,"tb-form-panel","stroked","no-padding-bottom",3,"formGroup"],["translate","",1,"tb-form-panel-title"],[3,"hintText"],[1,"tb-form-row","no-border","no-padding","tb-standard-fields","column-xs"],[1,"flex"],["translate",""],["matInput","","formControlName","serviceIdMetaDataAttribute"],["matInput","","formControlName","requestIdMetaDataAttribute"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"div",1),t.ɵɵtext(2,"tb.rulenode.reply-routing-configuration"),t.ɵɵelementEnd(),t.ɵɵelement(3,"tb-example-hint",2),t.ɵɵelementStart(4,"div",3)(5,"mat-form-field",4)(6,"mat-label",5),t.ɵɵtext(7,"tb.rulenode.service-id-metadata-attribute"),t.ɵɵelementEnd(),t.ɵɵelement(8,"input",6),t.ɵɵelementEnd(),t.ɵɵelementStart(9,"mat-form-field",4)(10,"mat-label",5),t.ɵɵtext(11,"tb.rulenode.request-id-metadata-attribute"),t.ɵɵelementEnd(),t.ɵɵelement(12,"input",7),t.ɵɵelementEnd()()()),2&e&&(t.ɵɵproperty("formGroup",n.sendRestApiCallReplyConfigForm),t.ɵɵadvance(3),t.ɵɵproperty("hintText","tb.rulenode.reply-routing-configuration-hint"))},dependencies:t.ɵɵgetComponentDepsFactory(fr),encapsulation:2})}}e("SendRestApiCallReplyConfigComponent",fr);const gr=["attributeChipList"],hr=()=>({standalone:!0});function yr(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",21),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=n.$implicit,r=t.ɵɵnextContext();t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,2,r.telemetryTypeTranslationsMap.get(e))," ")}}function br(e,n){if(1&e){const e=t.ɵɵgetCurrentView();t.ɵɵelementStart(0,"mat-chip-row",22),t.ɵɵlistener("removed",(function(){const n=t.ɵɵrestoreView(e).$implicit,r=t.ɵɵnextContext();return t.ɵɵresetView(r.removeKey(n))})),t.ɵɵtext(1),t.ɵɵelementStart(2,"mat-icon",23),t.ɵɵtext(3,"close"),t.ɵɵelementEnd()()}if(2&e){const e=n.$implicit;t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",e," ")}}function vr(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(2,1,"tb.rulenode.attributes-keys-required")))}function xr(e,n){1&e&&(t.ɵɵelementStart(0,"div",18),t.ɵɵpipe(1,"translate"),t.ɵɵelementStart(2,"mat-slide-toggle",24),t.ɵɵtext(3),t.ɵɵpipe(4,"translate"),t.ɵɵelementEnd()()),2&e&&(t.ɵɵpropertyInterpolate("tb-hint-tooltip-icon",t.ɵɵpipeBind1(1,2,"tb.rulenode.notify-device-on-delete-hint")),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(4,4,"tb.rulenode.notify-device")," "))}class Cr extends i{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopeMap=o,this.attributeScopes=Object.keys(o),this.telemetryTypeTranslationsMap=l,this.separatorKeysCodes=[U,H,z]}configForm(){return this.deleteAttributesConfigForm}onConfigurationSet(e){this.deleteAttributesConfigForm=this.fb.group({scope:[e?e.scope:null,[N.required]],keys:[e?e.keys:null,[N.required]],sendAttributesDeletedNotification:[!!e&&e.sendAttributesDeletedNotification,[]],notifyDevice:[!!e&&e.notifyDevice,[]]}),this.deleteAttributesConfigForm.get("scope").valueChanges.subscribe((e=>{e!==o.SHARED_SCOPE&&this.deleteAttributesConfigForm.get("notifyDevice").patchValue(!1,{emitEvent:!1})}))}removeKey(e){const t=this.deleteAttributesConfigForm.get("keys").value,n=t.indexOf(e);n>=0&&(t.splice(n,1),this.deleteAttributesConfigForm.get("keys").patchValue(t,{emitEvent:!0}))}addKey(e){const t=e.input;let n=e.value;if((n||"").trim()){n=n.trim();let e=this.deleteAttributesConfigForm.get("keys").value;e&&-1!==e.indexOf(n)||(e||(e=[]),e.push(n),this.deleteAttributesConfigForm.get("keys").patchValue(e,{emitEvent:!0}))}t&&(t.value="")}static{this.ɵfac=function(e){return new(e||Cr)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Cr,selectors:[["tb-action-node-delete-attributes-config"]],viewQuery:function(e,n){if(1&e&&t.ɵɵviewQuery(gr,5),2&e){let e;t.ɵɵqueryRefresh(e=t.ɵɵloadQuery())&&(n.attributeChipList=e.first)}},features:[t.ɵɵInheritDefinitionFeature],decls:41,vars:31,consts:[["attributeChipList",""],[1,"tb-form-panel","no-border","no-padding",3,"formGroup"],[1,"tb-form-panel","stroked"],[3,"hintText"],[1,"tb-form-row","no-border","no-padding","tb-standard-fields"],[1,"flex"],["required","","matInput","","formControlName","scope",1,"tb-entity-type-select"],[3,"value",4,"ngFor","ngForOf"],["type","text","matInput","","readonly","","disabled","",3,"ngModel","ngModelOptions"],["type","button","matSuffix","","mat-icon-button","","aria-label","Copy","ngxClipboard","",3,"cbContent","matTooltip"],["aria-hidden","false","aria-label","help-icon"],["subscriptSizing","dynamic",1,"mat-block"],["formControlName","keys"],[3,"removed",4,"ngFor","ngForOf"],["matInput","","type","text",3,"matChipInputTokenEnd","matChipInputFor","matChipInputSeparatorKeyCodes","matChipInputAddOnBlur"],[4,"ngIf"],["translate",""],[1,"tb-settings"],[1,"tb-form-row","no-border","no-padding",3,"tb-hint-tooltip-icon"],["formControlName","sendAttributesDeletedNotification",1,"mat-slide"],["class","tb-form-row no-border no-padding",3,"tb-hint-tooltip-icon",4,"ngIf"],[3,"value"],[3,"removed"],["matChipRemove",""],["formControlName","notifyDevice",1,"mat-slide"]],template:function(e,n){if(1&e){const e=t.ɵɵgetCurrentView();t.ɵɵelementStart(0,"section",1)(1,"div",2),t.ɵɵelement(2,"tb-example-hint",3),t.ɵɵelementStart(3,"div",4)(4,"mat-form-field",5)(5,"mat-label"),t.ɵɵtext(6),t.ɵɵpipe(7,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(8,"mat-select",6),t.ɵɵtemplate(9,yr,3,4,"mat-option",7),t.ɵɵelementEnd()(),t.ɵɵelementStart(10,"mat-form-field",5)(11,"mat-label"),t.ɵɵtext(12),t.ɵɵpipe(13,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(14,"input",8),t.ɵɵelementStart(15,"button",9),t.ɵɵpipe(16,"translate"),t.ɵɵelementStart(17,"mat-icon",10),t.ɵɵtext(18,"content_copy "),t.ɵɵelementEnd()()()()(),t.ɵɵelementStart(19,"mat-form-field",11)(20,"mat-label"),t.ɵɵtext(21),t.ɵɵpipe(22,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(23,"mat-chip-grid",12,0),t.ɵɵtemplate(25,br,4,1,"mat-chip-row",13),t.ɵɵelementStart(26,"input",14),t.ɵɵlistener("matChipInputTokenEnd",(function(r){return t.ɵɵrestoreView(e),t.ɵɵresetView(n.addKey(r))})),t.ɵɵelementEnd()(),t.ɵɵtemplate(27,vr,3,3,"mat-error",15),t.ɵɵelementStart(28,"mat-hint",16),t.ɵɵtext(29,"tb.rulenode.general-pattern-hint"),t.ɵɵelementEnd()(),t.ɵɵelementStart(30,"section",2)(31,"mat-expansion-panel",17)(32,"mat-expansion-panel-header")(33,"mat-panel-title",16),t.ɵɵtext(34,"tb.rulenode.advanced-settings"),t.ɵɵelementEnd()(),t.ɵɵelementStart(35,"div",18),t.ɵɵpipe(36,"translate"),t.ɵɵelementStart(37,"mat-slide-toggle",19),t.ɵɵtext(38),t.ɵɵpipe(39,"translate"),t.ɵɵelementEnd()(),t.ɵɵtemplate(40,xr,5,6,"div",20),t.ɵɵelementEnd()()()}if(2&e){const e=t.ɵɵreference(24);t.ɵɵproperty("formGroup",n.deleteAttributesConfigForm),t.ɵɵadvance(2),t.ɵɵproperty("hintText","tb.rulenode.attributes-scope-hint"),t.ɵɵadvance(4),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(7,18,"tb.rulenode.attributes-scope")),t.ɵɵadvance(3),t.ɵɵproperty("ngForOf",n.attributeScopes),t.ɵɵadvance(3),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(13,20,"tb.rulenode.attributes-scope-value")),t.ɵɵadvance(2),t.ɵɵproperty("ngModel",n.deleteAttributesConfigForm.get("scope").value)("ngModelOptions",t.ɵɵpureFunction0(30,hr)),t.ɵɵadvance(),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(16,22,"tb.rulenode.attributes-scope-value-copy")),t.ɵɵproperty("cbContent",n.deleteAttributesConfigForm.get("scope").value),t.ɵɵadvance(6),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(22,24,"tb.rulenode.attributes-keys")),t.ɵɵadvance(4),t.ɵɵproperty("ngForOf",n.deleteAttributesConfigForm.get("keys").value),t.ɵɵadvance(),t.ɵɵproperty("matChipInputFor",e)("matChipInputSeparatorKeyCodes",n.separatorKeysCodes)("matChipInputAddOnBlur",!0),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.deleteAttributesConfigForm.get("keys").hasError("required")),t.ɵɵadvance(8),t.ɵɵpropertyInterpolate("tb-hint-tooltip-icon",t.ɵɵpipeBind1(36,26,"tb.rulenode.send-attributes-deleted-notification-hint")),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(39,28,"tb.rulenode.send-attributes-deleted-notification")," "),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",n.deleteAttributesConfigForm.get("scope").value===n.attributeScopeMap.SHARED_SCOPE)}},dependencies:t.ɵɵgetComponentDepsFactory(Cr),encapsulation:2})}}e("DeleteAttributesConfigComponent",Cr);const Sr=(e,t)=>[e,t];function Tr(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error",8),t.ɵɵtext(1," tb.rulenode.custom-expression-field-input-required "),t.ɵɵelementEnd())}function Ir(e,n){if(1&e&&(t.ɵɵelementStart(0,"fieldset",2)(1,"legend",21),t.ɵɵtext(2),t.ɵɵpipe(3,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(4,"mat-form-field",22),t.ɵɵelement(5,"input",23),t.ɵɵtemplate(6,Tr,2,0,"mat-error",11),t.ɵɵelementStart(7,"mat-hint",8),t.ɵɵtext(8,"tb.rulenode.custom-expression-field-input-hint"),t.ɵɵelementEnd()()()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(2),t.ɵɵtextInterpolate1("",t.ɵɵpipeBind1(3,2,"tb.rulenode.custom-expression-field-input")," *"),t.ɵɵadvance(4),t.ɵɵproperty("ngIf",e.mathFunctionConfigForm.get("customFunction").hasError("required"))}}function Er(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",24),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementStart(3,"small",25),t.ɵɵtext(4),t.ɵɵelementEnd()()),2&e){const e=n.$implicit,r=t.ɵɵnextContext();t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,3,r.argumentTypeResultMap.get(e).name)," "),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",r.argumentTypeResultMap.get(e).description," ")}}function Fr(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error",8),t.ɵɵtext(1," tb.rulenode.type-field-input-required "),t.ɵɵelementEnd())}function qr(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",28),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=n.$implicit,r=t.ɵɵnextContext(2);t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,2,r.attributeScopeMap.get(e))," ")}}function Ar(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-form-field",7)(1,"mat-label",8),t.ɵɵtext(2,"tb.rulenode.attribute-scope-field-input"),t.ɵɵelementEnd(),t.ɵɵelementStart(3,"mat-select",26),t.ɵɵtemplate(4,qr,3,4,"mat-option",27),t.ɵɵelementEnd()()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(4),t.ɵɵproperty("ngForOf",e.attributeScopeResult)}}function kr(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error",8),t.ɵɵtext(1," tb.rulenode.key-field-input-required "),t.ɵɵelementEnd())}function Nr(e,n){1&e&&(t.ɵɵelementStart(0,"div",29)(1,"mat-checkbox",30),t.ɵɵtext(2),t.ɵɵpipe(3,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(4,"mat-checkbox",31),t.ɵɵtext(5),t.ɵɵpipe(6,"translate"),t.ɵɵelementEnd()()),2&e&&(t.ɵɵadvance(2),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(3,2,"tb.rulenode.add-to-message-field-input")," "),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(6,4,"tb.rulenode.add-to-metadata-field-input")," "))}class wr extends i{constructor(e,t){super(e),this.store=e,this.fb=t,this.MathFunction=Rt,this.ArgumentTypeResult=Gt,this.argumentTypeResultMap=Qt,this.attributeScopeMap=Xt,this.argumentsResult=Object.values(Gt),this.attributeScopeResult=Object.values(Wt)}configForm(){return this.mathFunctionConfigForm}onConfigurationSet(e){this.mathFunctionConfigForm=this.fb.group({operation:[e?e.operation:null,[N.required]],arguments:[e?e.arguments:null,[N.required]],customFunction:[e?e.customFunction:"",[N.required]],result:this.fb.group({type:[e?e.result.type:null,[N.required]],attributeScope:[e?e.result.attributeScope:null,[N.required]],key:[e?e.result.key:"",[N.required]],resultValuePrecision:[e?e.result.resultValuePrecision:0],addToBody:[!!e&&e.result.addToBody],addToMetadata:[!!e&&e.result.addToMetadata]})})}updateValidators(e){const t=this.mathFunctionConfigForm.get("operation").value,n=this.mathFunctionConfigForm.get("result.type").value;t===Rt.CUSTOM?(this.mathFunctionConfigForm.get("customFunction").enable({emitEvent:!1}),null===this.mathFunctionConfigForm.get("customFunction").value&&this.mathFunctionConfigForm.get("customFunction").patchValue("(x - 32) / 1.8",{emitEvent:!1})):this.mathFunctionConfigForm.get("customFunction").disable({emitEvent:!1}),n===Gt.ATTRIBUTE?this.mathFunctionConfigForm.get("result.attributeScope").enable({emitEvent:!1}):this.mathFunctionConfigForm.get("result.attributeScope").disable({emitEvent:!1}),this.mathFunctionConfigForm.get("customFunction").updateValueAndValidity({emitEvent:e}),this.mathFunctionConfigForm.get("result.attributeScope").updateValueAndValidity({emitEvent:e})}validatorTriggers(){return["operation","result.type"]}static{this.ɵfac=function(e){return new(e||wr)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:wr,selectors:[["tb-action-node-math-function-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:39,vars:23,consts:[[1,"flex","flex-col",3,"formGroup"],["required","","formControlName","operation",1,"flex-full","max-h-30%","xs:max-h-full","md:max-h-full"],[1,"fields-group","flex","flex-col","gap-2"],["translate","",1,"group-title"],["formControlName","arguments",3,"function"],["class","fields-group flex flex-col gap-2",4,"ngIf"],["formGroupName","result"],[1,"mat-block","flex-1"],["translate",""],["formControlName","type","required",""],["style","border-bottom: 1px solid #eee;",3,"value",4,"ngFor","ngForOf"],["translate","",4,"ngIf"],[1,"xs:flex-col","gt-xs:gap-4","flex","flex-1","flex-row"],["class","mat-block flex-1",4,"ngIf"],["floatLabel","always",1,"mat-block","flex-1"],["matInput","","formControlName","key","required",""],["aria-hidden","false","aria-label","help-icon","matSuffix","","color","primary",1,"help-icon","margin-8","cursor-pointer",3,"matTooltip"],["floatLabel","always","subscriptSizing","dynamic",1,"mat-block","flex-1"],["formControlName","resultValuePrecision","matInput","","step","1","min","0","type","number"],[3,"innerHTML"],["class","xs:flex-col gt-xs:gap-4 flex flex-1 flex-row items-stretch justify-start","style","padding-top: 16px;",4,"ngIf"],[1,"group-title"],["subscriptSizing","dynamic",1,"mat-block","no-margin-top","flex-1"],["matInput","","formControlName","customFunction","required",""],[2,"border-bottom","1px solid #eee",3,"value"],[2,"display","block","overflow","hidden","text-overflow","ellipsis","white-space","nowrap"],["required","","formControlName","attributeScope"],[3,"value",4,"ngFor","ngForOf"],[3,"value"],[1,"xs:flex-col","gt-xs:gap-4","flex","flex-1","flex-row","items-stretch","justify-start",2,"padding-top","16px"],["formControlName","addToBody"],["formControlName","addToMetadata"]],template:function(e,n){if(1&e&&(t.ɵɵelementStart(0,"section",0),t.ɵɵelement(1,"tb-math-function-autocomplete",1),t.ɵɵelementStart(2,"fieldset",2)(3,"legend",3),t.ɵɵtext(4,"tb.rulenode.argument-tile"),t.ɵɵelementEnd(),t.ɵɵelement(5,"tb-arguments-map-config",4),t.ɵɵelementEnd(),t.ɵɵtemplate(6,Ir,9,4,"fieldset",5),t.ɵɵelementStart(7,"fieldset",2)(8,"legend",3),t.ɵɵtext(9,"tb.rulenode.result-title"),t.ɵɵelementEnd(),t.ɵɵelementStart(10,"div",6)(11,"mat-form-field",7)(12,"mat-label",8),t.ɵɵtext(13,"tb.rulenode.type-field-input"),t.ɵɵelementEnd(),t.ɵɵelementStart(14,"mat-select",9)(15,"mat-select-trigger"),t.ɵɵtext(16),t.ɵɵpipe(17,"translate"),t.ɵɵelementEnd(),t.ɵɵtemplate(18,Er,5,5,"mat-option",10),t.ɵɵelementEnd(),t.ɵɵtemplate(19,Fr,2,0,"mat-error",11),t.ɵɵelementEnd(),t.ɵɵelementStart(20,"div",12),t.ɵɵtemplate(21,Ar,5,1,"mat-form-field",13),t.ɵɵelementStart(22,"mat-form-field",14)(23,"mat-label",8),t.ɵɵtext(24,"tb.rulenode.key-field-input"),t.ɵɵelementEnd(),t.ɵɵelement(25,"input",15),t.ɵɵelementStart(26,"mat-icon",16),t.ɵɵpipe(27,"translate"),t.ɵɵtext(28,"help"),t.ɵɵelementEnd(),t.ɵɵtemplate(29,kr,2,0,"mat-error",11),t.ɵɵelementEnd()(),t.ɵɵelementStart(30,"div",12)(31,"mat-form-field",17)(32,"mat-label",8),t.ɵɵtext(33,"tb.rulenode.number-floating-point-field-input"),t.ɵɵelementEnd(),t.ɵɵelement(34,"input",18)(35,"mat-hint",19),t.ɵɵpipe(36,"translate"),t.ɵɵpipe(37,"safe"),t.ɵɵelementEnd()(),t.ɵɵtemplate(38,Nr,7,6,"div",20),t.ɵɵelementEnd()()()),2&e){let e;t.ɵɵproperty("formGroup",n.mathFunctionConfigForm),t.ɵɵadvance(5),t.ɵɵproperty("function",n.mathFunctionConfigForm.get("operation").value),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.mathFunctionConfigForm.get("operation").value===n.MathFunction.CUSTOM),t.ɵɵadvance(10),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(17,11,null==(e=n.argumentTypeResultMap.get(n.mathFunctionConfigForm.get("result.type").value))?null:e.name)," "),t.ɵɵadvance(2),t.ɵɵproperty("ngForOf",n.argumentsResult),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.mathFunctionConfigForm.get("result.type").hasError("required")),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",n.mathFunctionConfigForm.get("result").get("type").value===n.ArgumentTypeResult.ATTRIBUTE),t.ɵɵadvance(5),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(27,13,"tb.rulenode.math-templatization-tooltip")),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",n.mathFunctionConfigForm.get("result.key").hasError("required")),t.ɵɵadvance(6),t.ɵɵproperty("innerHTML",t.ɵɵpipeBind2(37,17,t.ɵɵpipeBind1(36,15,"tb.rulenode.number-floating-point-field-input-hint"),"html"),t.ɵɵsanitizeHtml),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",t.ɵɵpureFunction2(20,Sr,n.ArgumentTypeResult.ATTRIBUTE,n.ArgumentTypeResult.TIME_SERIES).includes(n.mathFunctionConfigForm.get("result").get("type").value))}},dependencies:t.ɵɵgetComponentDepsFactory(wr),styles:["[_nghost-%COMP%] .fields-group{padding:0 16px 8px;margin:10px 0;border:1px groove rgba(0,0,0,.25);border-radius:4px}[_nghost-%COMP%] .fields-group .mat-mdc-form-field .mat-mdc-form-field-infix{width:100%}[_nghost-%COMP%] .fields-group legend{color:#000000b3;width:fit-content}[_nghost-%COMP%] .fields-group legend+*{display:block}[_nghost-%COMP%] .fields-group legend+*.no-margin-top{margin-top:0}"]})}}function Mr(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",4),t.ɵɵtext(1),t.ɵɵelementEnd()),2&e){const e=n.$implicit,r=t.ɵɵnextContext();t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",r.messageTypeNames.get(e)," ")}}e("MathFunctionConfigComponent",wr);class Br extends i{constructor(e,t){super(e),this.store=e,this.fb=t,this.messageTypeNames=f,this.eventOptions=[g.CONNECT_EVENT,g.ACTIVITY_EVENT,g.DISCONNECT_EVENT,g.INACTIVITY_EVENT]}configForm(){return this.deviceState}prepareInputConfig(e){return{event:P(e?.event)?e.event:g.ACTIVITY_EVENT}}onConfigurationSet(e){this.deviceState=this.fb.group({event:[e.event,[N.required]]})}static{this.ɵfac=function(e){return new(e||Br)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.FormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Br,selectors:[["tb-action-node-device-state-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:7,vars:5,consts:[[3,"formGroup"],["subscriptSizing","dynamic",1,"mat-block"],["formControlName","event"],[3,"value",4,"ngFor","ngForOf"],[3,"value"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"mat-form-field",1)(2,"mat-label"),t.ɵɵtext(3),t.ɵɵpipe(4,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(5,"mat-select",2),t.ɵɵtemplate(6,Mr,2,2,"mat-option",3),t.ɵɵelementEnd()()()),2&e&&(t.ɵɵproperty("formGroup",n.deviceState),t.ɵɵadvance(3),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(4,3,"tb.rulenode.select-device-connectivity-event")),t.ɵɵadvance(3),t.ɵɵproperty("ngForOf",n.eventOptions))},dependencies:t.ɵɵgetComponentDepsFactory(Br),encapsulation:2})}}e("DeviceStateConfigComponent",Br);const Vr=(e,t)=>({valText:e,keyText:t});function Or(e,n){if(1&e&&(t.ɵɵelementStart(0,"div",13),t.ɵɵtext(1),t.ɵɵelementEnd()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",e.requiredText," ")}}function Dr(e,n){1&e&&(t.ɵɵelementStart(0,"div",13),t.ɵɵtext(1," tb.rulenode.map-fields-required "),t.ɵɵelementEnd())}function Lr(e,n){if(1&e&&(t.ɵɵelementStart(0,"div",13),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind2(2,1,"tb.key-val.unique-key-value-pair-error",t.ɵɵpureFunction2(4,Vr,e.valText,e.keyText))," ")}}function Pr(e,n){if(1&e){const e=t.ɵɵgetCurrentView();t.ɵɵelementStart(0,"div",14)(1,"mat-form-field",15),t.ɵɵelement(2,"input",16),t.ɵɵelementEnd(),t.ɵɵelementStart(3,"mat-form-field",15),t.ɵɵelement(4,"input",16),t.ɵɵelementEnd(),t.ɵɵelementStart(5,"div",17)(6,"button",18),t.ɵɵpipe(7,"translate"),t.ɵɵlistener("click",(function(){const n=t.ɵɵrestoreView(e).index,r=t.ɵɵnextContext();return t.ɵɵresetView(r.removeKeyVal(n))})),t.ɵɵelementStart(8,"mat-icon"),t.ɵɵtext(9,"delete"),t.ɵɵelementEnd()()()()}if(2&e){const e=n.$implicit,r=t.ɵɵnextContext();t.ɵɵadvance(2),t.ɵɵproperty("placeholder",r.keyText+"*")("formControl",e.get("key")),t.ɵɵadvance(2),t.ɵɵproperty("placeholder",r.valText+"*")("formControl",e.get("value")),t.ɵɵadvance(2),t.ɵɵclassProp("tb-hidden",1===r.keyValsFormArray().controls.length),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(7,8,"tb.key-val.remove-mapping-entry")),t.ɵɵproperty("disabled",r.disabled)}}function Rr(e,n){if(1&e&&t.ɵɵelement(0,"tb-example-hint",19),2&e){const e=t.ɵɵnextContext();t.ɵɵproperty("hintText",e.hintText)("popupHelpLink",e.popupHelpLink)}}class _r{constructor(e,t){this.injector=e,this.fb=t,this.propagateChange=()=>{},this.destroy$=new Y,this.disabled=!1,this.uniqueKeyValuePairValidator=!1,this.required=!1,this.duplicateValuesValidator=e=>e.controls.key.value===e.controls.value.value&&e.controls.key.value&&e.controls.value.value?{uniqueKeyValuePair:!0}:null,this.oneMapRequiredValidator=e=>e.get("keyVals").value.length,this.propagateNestedErrors=e=>{if(this.kvListFormGroup&&this.kvListFormGroup.get("keyVals")&&"VALID"===this.kvListFormGroup.get("keyVals")?.status)return null;const t={};if(this.kvListFormGroup&&this.kvListFormGroup.setErrors(null),e instanceof w||e instanceof M){if(e.errors)for(const n of Object.keys(e.errors))t[n]=!0;for(const n of Object.keys(e.controls)){const r=this.propagateNestedErrors(e.controls[n]);if(r&&Object.keys(r).length)for(const e of Object.keys(r))t[e]=!0}return t}if(e.errors)for(const n of Object.keys(e.errors))t[n]=!0;return R(t,{})?null:t}}ngOnInit(){this.ngControl=this.injector.get(B),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({keyVals:this.fb.array([])},{validators:[this.propagateNestedErrors,this.oneMapRequiredValidator]}),this.kvListFormGroup.valueChanges.pipe(W(this.destroy$)).subscribe((()=>{this.updateModel()}))}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}keyValsFormArray(){return this.kvListFormGroup.get("keyVals")}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})}writeValue(e){const t=Object.keys(e).map((t=>({key:t,value:e[t]})));if(this.keyValsFormArray().length===t.length)this.keyValsFormArray().patchValue(t,{emitEvent:!1});else{const e=[];t.forEach((t=>{e.push(this.fb.group({key:[t.key,[N.required,N.pattern(/(?:.|\s)*\S(&:.|\s)*/)]],value:[t.value,[N.required,N.pattern(/(?:.|\s)*\S(&:.|\s)*/)]]},{validators:this.uniqueKeyValuePairValidator?[this.duplicateValuesValidator]:[]}))})),this.kvListFormGroup.setControl("keyVals",this.fb.array(e,this.propagateNestedErrors),{emitEvent:!1})}}removeKeyVal(e){this.keyValsFormArray().removeAt(e)}addKeyVal(){this.keyValsFormArray().push(this.fb.group({key:["",[N.required,N.pattern(/(?:.|\s)*\S(&:.|\s)*/)]],value:["",[N.required,N.pattern(/(?:.|\s)*\S(&:.|\s)*/)]]},{validators:this.uniqueKeyValuePairValidator?[this.duplicateValuesValidator]:[]}))}validate(){const e=this.kvListFormGroup.get("keyVals").value;if(!e.length&&this.required)return{kvMapRequired:!0};if(!this.kvListFormGroup.valid)return{kvFieldsRequired:!0};if(this.uniqueKeyValuePairValidator)for(const t of e)if(t.key===t.value)return{uniqueKeyValuePair:!0};return null}updateModel(){const e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{const t={};e.forEach((e=>{t[e.key]=e.value})),this.propagateChange(t)}}static{this.ɵfac=function(e){return new(e||_r)(t.ɵɵdirectiveInject(t.Injector),t.ɵɵdirectiveInject(k.FormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:_r,selectors:[["tb-kv-map-config"]],inputs:{disabled:"disabled",uniqueKeyValuePairValidator:"uniqueKeyValuePairValidator",labelText:"labelText",requiredText:"requiredText",keyText:"keyText",keyRequiredText:"keyRequiredText",valText:"valText",valRequiredText:"valRequiredText",hintText:"hintText",popupHelpLink:"popupHelpLink",required:"required"},features:[t.ɵɵProvidersFeature([{provide:V,useExisting:r((()=>_r)),multi:!0},{provide:O,useExisting:r((()=>_r)),multi:!0}])],decls:22,vars:12,consts:[[1,"tb-form-panel","stroked",3,"formGroup"],[1,"tb-form-row","no-padding","no-border","space-between"],[1,"tb-form-panel-title"],["class","tb-form-panel-hint tb-error","translate","",4,"ngIf"],[1,"tb-form-panel","no-border","no-padding"],[1,"tb-form-table"],[1,"tb-form-table-header"],[1,"tb-form-table-header-cell","field-space"],[1,"tb-form-table-header-cell","actions-header"],[1,"tb-form-table-body"],["class","tb-form-table-row",4,"ngFor","ngForOf"],["type","button","mat-stroked-button","","color","primary",3,"click"],[3,"hintText","popupHelpLink",4,"ngIf"],["translate","",1,"tb-form-panel-hint","tb-error"],[1,"tb-form-table-row"],["appearance","outline","subscriptSizing","dynamic",1,"tb-inline-field","field-space"],["matInput","",3,"placeholder","formControl"],[1,"tb-form-table-row-cell-buttons"],["type","button","mat-icon-button","","matTooltipPosition","above",3,"click","disabled","matTooltip"],[3,"hintText","popupHelpLink"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"div",1)(2,"div",2),t.ɵɵtext(3),t.ɵɵelementEnd(),t.ɵɵtemplate(4,Or,2,1,"div",3)(5,Dr,2,0,"div",3)(6,Lr,3,7,"div",3),t.ɵɵelementEnd(),t.ɵɵelementStart(7,"div",4)(8,"div",5)(9,"div",6)(10,"div",7),t.ɵɵtext(11),t.ɵɵelementEnd(),t.ɵɵelementStart(12,"div",7),t.ɵɵtext(13),t.ɵɵelementEnd(),t.ɵɵelement(14,"div",8),t.ɵɵelementEnd(),t.ɵɵelementStart(15,"div",9),t.ɵɵtemplate(16,Pr,10,10,"div",10),t.ɵɵelementEnd()()(),t.ɵɵelementStart(17,"div")(18,"button",11),t.ɵɵlistener("click",(function(){return n.addKeyVal()})),t.ɵɵtext(19),t.ɵɵpipe(20,"translate"),t.ɵɵelementEnd()(),t.ɵɵtemplate(21,Rr,1,2,"tb-example-hint",12),t.ɵɵelementEnd()),2&e&&(t.ɵɵproperty("formGroup",n.kvListFormGroup),t.ɵɵadvance(3),t.ɵɵtextInterpolate(n.labelText),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.kvListFormGroup.hasError("kvMapRequired")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.kvListFormGroup.hasError("required")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.kvListFormGroup.hasError("uniqueKeyValuePair")),t.ɵɵadvance(5),t.ɵɵtextInterpolate(n.keyText),t.ɵɵadvance(2),t.ɵɵtextInterpolate(n.valText),t.ɵɵadvance(3),t.ɵɵproperty("ngForOf",n.keyValsFormArray().controls),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(20,10,"tb.key-val.add-mapping-entry")," "),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",n.popupHelpLink||n.hintText))},dependencies:t.ɵɵgetComponentDepsFactory(_r),styles:["[_nghost-%COMP%] .field-space[_ngcontent-%COMP%]{flex:1 1 50%}[_nghost-%COMP%] .actions-header[_ngcontent-%COMP%]{width:40px}"]})}}e("KvMapConfigComponent",_r),J([h()],_r.prototype,"disabled",void 0),J([h()],_r.prototype,"uniqueKeyValuePairValidator",void 0),J([h()],_r.prototype,"required",void 0);const jr=e=>({inputName:e});function Gr(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",13),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementStart(3,"span",3),t.ɵɵtext(4,"tb.rulenode.relations-query-config-direction-suffix"),t.ɵɵelementEnd()()),2&e){const e=n.$implicit,r=t.ɵɵnextContext();t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,2,r.directionTypeTranslations.get(e))," ")}}function Kr(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.max-relation-level-error")," "))}function Ur(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.max-relation-level-invalid")," "))}function Hr(e,n){1&e&&(t.ɵɵelementStart(0,"div",14),t.ɵɵpipe(1,"translate"),t.ɵɵelementStart(2,"mat-slide-toggle",15),t.ɵɵtext(3),t.ɵɵpipe(4,"translate"),t.ɵɵelementEnd()()),2&e&&(t.ɵɵpropertyInterpolate("tb-hint-tooltip-icon",t.ɵɵpipeBind1(1,2,"tb.rulenode.last-level-device-relation-tooltip")),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(4,4,"alias.last-level-relation")," "))}class zr extends y{get required(){return this.requiredValue}set required(e){this.requiredValue=Z(e)}constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.values(d),this.directionTypeTranslations=b,this.entityType=u,this.propagateChange=null}ngOnInit(){this.deviceRelationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[N.required]],maxLevel:[null,[N.min(1)]],relationType:[null],deviceTypes:[null,[N.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((e=>{this.deviceRelationsQueryFormGroup.valid?this.propagateChange(e):this.propagateChange(null)}))}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})}writeValue(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})}static{this.ɵfac=function(e){return new(e||zr)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.FormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:zr,selectors:[["tb-device-relations-query-config"]],inputs:{disabled:"disabled",required:"required"},features:[t.ɵɵProvidersFeature([{provide:V,useExisting:r((()=>zr)),multi:!0}]),t.ɵɵInheritDefinitionFeature],decls:24,vars:26,consts:[[1,"flex","flex-col",3,"formGroup"],[1,"flex","flex-row","gap-5.5"],["subscriptSizing","dynamic","hideRequiredMarker","",1,"mat-block","max-w-50%","flex-full",2,"min-width","100px"],["translate",""],["required","","formControlName","direction"],[3,"value",4,"ngFor","ngForOf"],["floatLabel","always",1,"mat-block","max-w-50%","flex-full"],["matInput","","type","text","pattern","[0-9]*","inputmode","numeric","min","1","formControlName","maxLevel",3,"placeholder"],[4,"ngIf"],["class","tb-form-row no-border no-padding last-level-slide-toggle",3,"tb-hint-tooltip-icon",4,"ngIf"],["formControlName","relationType",1,"flex-1"],["required","","formControlName","deviceTypes",3,"label","entityType","emptyInputPlaceholder","filledInputPlaceholder"],["matSuffix","","aria-hidden","false","aria-label","help-icon","color","primary",1,"help-icon","margin-8","cursor-pointer",3,"matTooltip"],[3,"value"],[1,"tb-form-row","no-border","no-padding","last-level-slide-toggle",3,"tb-hint-tooltip-icon"],["formControlName","fetchLastLevelOnly",1,"mat-slide"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"div",1)(2,"mat-form-field",2)(3,"mat-label",3),t.ɵɵtext(4,"relation.direction"),t.ɵɵelementEnd(),t.ɵɵelementStart(5,"mat-select",4),t.ɵɵtemplate(6,Gr,5,4,"mat-option",5),t.ɵɵelementEnd()(),t.ɵɵelementStart(7,"mat-form-field",6)(8,"mat-label",3),t.ɵɵtext(9,"tb.rulenode.max-relation-level"),t.ɵɵelementEnd(),t.ɵɵelement(10,"input",7),t.ɵɵpipe(11,"translate"),t.ɵɵtemplate(12,Kr,3,3,"mat-error",8)(13,Ur,3,3,"mat-error",8),t.ɵɵelementEnd()(),t.ɵɵtemplate(14,Hr,5,6,"div",9),t.ɵɵelement(15,"tb-relation-type-autocomplete",10),t.ɵɵelementStart(16,"tb-entity-subtype-list",11),t.ɵɵpipe(17,"translate"),t.ɵɵpipe(18,"translate"),t.ɵɵpipe(19,"translate"),t.ɵɵelementStart(20,"mat-icon",12),t.ɵɵpipe(21,"translate"),t.ɵɵpipe(22,"translate"),t.ɵɵtext(23,"help"),t.ɵɵelementEnd()()()),2&e&&(t.ɵɵproperty("formGroup",n.deviceRelationsQueryFormGroup),t.ɵɵadvance(6),t.ɵɵproperty("ngForOf",n.directionTypes),t.ɵɵadvance(4),t.ɵɵpropertyInterpolate("placeholder",t.ɵɵpipeBind1(11,11,"tb.rulenode.unlimited-level")),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",n.deviceRelationsQueryFormGroup.get("maxLevel").hasError("min")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.deviceRelationsQueryFormGroup.get("maxLevel").invalid),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.deviceRelationsQueryFormGroup.get("maxLevel").value>1),t.ɵɵadvance(2),t.ɵɵproperty("label",t.ɵɵpipeBind1(17,13,"tb.rulenode.device-profiles"))("entityType",n.entityType.DEVICE)("emptyInputPlaceholder",t.ɵɵpipeBind1(18,15,"tb.rulenode.add-device-profile"))("filledInputPlaceholder",t.ɵɵpipeBind1(19,17,"tb.rulenode.add-device-profile")),t.ɵɵadvance(4),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind2(22,21,"tb.rulenode.chip-help",t.ɵɵpureFunction1(24,jr,t.ɵɵpipeBind1(21,19,"tb.rulenode.device-profile")))))},dependencies:t.ɵɵgetComponentDepsFactory(zr),styles:["[_nghost-%COMP%] .last-level-slide-toggle[_ngcontent-%COMP%]{margin:8px 0 24px}"]})}}function $r(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",13),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementStart(3,"span",4),t.ɵɵtext(4,"tb.rulenode.relations-query-config-direction-suffix"),t.ɵɵelementEnd()()),2&e){const e=n.$implicit,r=t.ɵɵnextContext();t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,2,r.directionTypeTranslations.get(e))," ")}}function Qr(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.max-relation-level-error")," "))}function Jr(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.max-relation-level-invalid")," "))}function Yr(e,n){1&e&&(t.ɵɵelementStart(0,"div",14),t.ɵɵpipe(1,"translate"),t.ɵɵelementStart(2,"mat-slide-toggle",15),t.ɵɵtext(3),t.ɵɵpipe(4,"translate"),t.ɵɵelementEnd()()),2&e&&(t.ɵɵpropertyInterpolate("tb-hint-tooltip-icon",t.ɵɵpipeBind1(1,2,"tb.rulenode.last-level-relation-tooltip")),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(4,4,"alias.last-level-relation")," "))}e("DeviceRelationsQueryConfigComponent",zr);class Wr extends y{get required(){return this.requiredValue}set required(e){this.requiredValue=Z(e)}constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.values(d),this.directionTypeTranslations=b,this.propagateChange=null}ngOnInit(){this.relationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[N.required]],maxLevel:[null,[N.min(1)]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((e=>{this.relationsQueryFormGroup.valid?this.propagateChange(e):this.propagateChange(null)}))}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})}writeValue(e){this.relationsQueryFormGroup.reset(e||{},{emitEvent:!1})}static{this.ɵfac=function(e){return new(e||Wr)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.FormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Wr,selectors:[["tb-relations-query-config"]],inputs:{disabled:"disabled",required:"required"},features:[t.ɵɵProvidersFeature([{provide:V,useExisting:r((()=>Wr)),multi:!0}]),t.ɵɵInheritDefinitionFeature],decls:22,vars:9,consts:[[1,"tb-form-panel","stroked",3,"formGroup"],["translate","",1,"tb-form-panel-title","tb-required"],[1,"flex","flex-row","gap-4"],["hideRequiredMarker","",1,"mat-block","max-w-50%","flex-full",2,"min-width","100px"],["translate",""],["required","","formControlName","direction"],[3,"value",4,"ngFor","ngForOf"],["floatLabel","always",1,"mat-block","max-w-50%","flex-full"],["matInput","","type","text","pattern","[0-9]*","min","1","inputmode","numeric","formControlName","maxLevel",3,"placeholder"],[4,"ngIf"],["class","tb-form-row no-border no-padding last-level-slide-toggle",3,"tb-hint-tooltip-icon",4,"ngIf"],["translate","",1,"tb-form-panel-title"],["formControlName","filters"],[3,"value"],[1,"tb-form-row","no-border","no-padding","last-level-slide-toggle",3,"tb-hint-tooltip-icon"],["formControlName","fetchLastLevelOnly",1,"mat-slide"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"div",1),t.ɵɵtext(2,"tb.rulenode.relations-query"),t.ɵɵelementEnd(),t.ɵɵelementStart(3,"section")(4,"div",2)(5,"mat-form-field",3)(6,"mat-label",4),t.ɵɵtext(7,"relation.direction"),t.ɵɵelementEnd(),t.ɵɵelementStart(8,"mat-select",5),t.ɵɵtemplate(9,$r,5,4,"mat-option",6),t.ɵɵelementEnd()(),t.ɵɵelementStart(10,"mat-form-field",7)(11,"mat-label",4),t.ɵɵtext(12,"tb.rulenode.max-relation-level"),t.ɵɵelementEnd(),t.ɵɵelement(13,"input",8),t.ɵɵpipe(14,"translate"),t.ɵɵtemplate(15,Qr,3,3,"mat-error",9)(16,Jr,3,3,"mat-error",9),t.ɵɵelementEnd()(),t.ɵɵtemplate(17,Yr,5,6,"div",10),t.ɵɵelementEnd(),t.ɵɵelementStart(18,"section",0)(19,"div",11),t.ɵɵtext(20,"relation.relation-filters"),t.ɵɵelementEnd(),t.ɵɵelement(21,"tb-relation-filters",12),t.ɵɵelementEnd()()),2&e&&(t.ɵɵproperty("formGroup",n.relationsQueryFormGroup),t.ɵɵadvance(9),t.ɵɵproperty("ngForOf",n.directionTypes),t.ɵɵadvance(4),t.ɵɵpropertyInterpolate("placeholder",t.ɵɵpipeBind1(14,7,"tb.rulenode.unlimited-level")),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",n.relationsQueryFormGroup.get("maxLevel").hasError("min")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.relationsQueryFormGroup.get("maxLevel").invalid),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.relationsQueryFormGroup.get("maxLevel").value>1),t.ɵɵadvance(),t.ɵɵproperty("formGroup",n.relationsQueryFormGroup))},dependencies:t.ɵɵgetComponentDepsFactory(Wr),encapsulation:2})}}e("RelationsQueryConfigComponent",Wr);const Xr=["chipList"],Zr=["messageTypeAutocomplete"],ea=["messageTypeInput"],ta=e=>({inputName:e}),na=e=>({messageType:e});function ra(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-label"),t.ɵɵtext(1),t.ɵɵelementEnd()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(),t.ɵɵtextInterpolate(e.label)}}function aa(e,n){if(1&e){const e=t.ɵɵgetCurrentView();t.ɵɵelementStart(0,"mat-chip-row",13),t.ɵɵlistener("removed",(function(){const n=t.ɵɵrestoreView(e).$implicit,r=t.ɵɵnextContext();return t.ɵɵresetView(r.remove(n))})),t.ɵɵtext(1),t.ɵɵelementStart(2,"mat-icon",14),t.ɵɵtext(3,"close"),t.ɵɵelementEnd()()}if(2&e){const e=n.$implicit;t.ɵɵproperty("removable",!0),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",e.name," ")}}function ia(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",15),t.ɵɵelement(1,"span",16),t.ɵɵpipe(2,"highlight"),t.ɵɵelementEnd()),2&e){const e=n.$implicit,r=t.ɵɵnextContext();t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵproperty("innerHTML",t.ɵɵpipeBind2(2,2,e.name,r.searchText),t.ɵɵsanitizeHtml)}}function oa(e,n){1&e&&(t.ɵɵelementStart(0,"div")(1,"span",21),t.ɵɵtext(2,"tb.rulenode.no-message-types-found"),t.ɵɵelementEnd()())}function la(e,n){if(1&e&&(t.ɵɵelementStart(0,"span"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=t.ɵɵnextContext(2);t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind2(2,1,"tb.rulenode.no-message-type-matching",t.ɵɵpureFunction1(4,na,e.truncate.transform(e.searchText,!0,6,"...")))," ")}}function sa(e,n){if(1&e){const e=t.ɵɵgetCurrentView();t.ɵɵelementStart(0,"mat-option",17)(1,"div",18),t.ɵɵlistener("click",(function(n){return t.ɵɵrestoreView(e),t.ɵɵresetView(n.stopPropagation())})),t.ɵɵtemplate(2,oa,3,0,"div",19)(3,la,3,6,"ng-template",null,3,t.ɵɵtemplateRefExtractor),t.ɵɵelementStart(5,"span")(6,"a",20),t.ɵɵlistener("click",(function(n){t.ɵɵrestoreView(e);const r=t.ɵɵnextContext();return t.ɵɵresetView(r.createMessageType(n,r.searchText))})),t.ɵɵtext(7,"tb.rulenode.create-new-message-type"),t.ɵɵelementEnd()()()()}if(2&e){const e=t.ɵɵreference(4),n=t.ɵɵnextContext();t.ɵɵproperty("value",null),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",!n.textIsNotEmpty(n.searchText))("ngIfElse",e)}}function pa(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.select-message-types-required")," "))}class ma extends y{get required(){return this.requiredValue}set required(e){this.requiredValue=Z(e)}constructor(e,t,n,r){super(e),this.store=e,this.translate=t,this.truncate=n,this.fb=r,this.placeholder="tb.rulenode.add-message-type",this.separatorKeysCodes=[U,H,z],this.messageTypes=[],this.messageTypesList=[],this.searchText="",this.propagateChange=e=>{},this.messageTypeConfigForm=this.fb.group({messageType:[null]});for(const e of Object.keys(g))this.messageTypesList.push({name:f.get(g[e]),value:e})}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}ngOnInit(){this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(ee(""),te((e=>e||"")),ne((e=>this.fetchMessageTypes(e))),re())}setDisabledState(e){this.disabled=e,this.disabled?this.messageTypeConfigForm.disable({emitEvent:!1}):this.messageTypeConfigForm.enable({emitEvent:!1})}writeValue(e){this.searchText="",this.messageTypes.length=0,e&&e.forEach((e=>{const t=this.messageTypesList.find((t=>t.value===e));t?this.messageTypes.push({name:t.name,value:t.value}):this.messageTypes.push({name:e,value:e})}))}displayMessageTypeFn(e){return e?e.name:void 0}textIsNotEmpty(e){return e&&e.length>0}createMessageType(e,t){e.preventDefault(),this.transformMessageType(t)}add(e){this.transformMessageType(e.value)}fetchMessageTypes(e){if(this.searchText=e,this.searchText&&this.searchText.length){const e=this.searchText.toUpperCase();return X(this.messageTypesList.filter((t=>t.name.toUpperCase().includes(e))))}return X(this.messageTypesList)}transformMessageType(e){if((e||"").trim()){let t;const n=e.trim(),r=this.messageTypesList.find((e=>e.name===n));t=r?{name:r.name,value:r.value}:{name:n,value:n},t&&this.addMessageType(t)}this.clear("")}remove(e){const t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())}selected(e){this.addMessageType(e.option.value),this.clear("")}addMessageType(e){-1===this.messageTypes.findIndex((t=>t.value===e.value))&&(this.messageTypes.push(e),this.updateModel())}onFocus(){this.messageTypeConfigForm.get("messageType").updateValueAndValidity({onlySelf:!0,emitEvent:!0})}clear(e=""){this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((()=>{this.messageTypeInput.nativeElement.blur(),this.messageTypeInput.nativeElement.focus()}),0)}updateModel(){const e=this.messageTypes.map((e=>e.value));this.required?(this.chipList.errorState=!e.length,this.propagateChange(e.length>0?e:null)):(this.chipList.errorState=!1,this.propagateChange(e))}static{this.ɵfac=function(e){return new(e||ma)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(K.TranslateService),t.ɵɵdirectiveInject(v.TruncatePipe),t.ɵɵdirectiveInject(k.FormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:ma,selectors:[["tb-message-types-config"]],viewQuery:function(e,n){if(1&e&&(t.ɵɵviewQuery(Xr,5),t.ɵɵviewQuery(Zr,5),t.ɵɵviewQuery(ea,5)),2&e){let e;t.ɵɵqueryRefresh(e=t.ɵɵloadQuery())&&(n.chipList=e.first),t.ɵɵqueryRefresh(e=t.ɵɵloadQuery())&&(n.matAutocomplete=e.first),t.ɵɵqueryRefresh(e=t.ɵɵloadQuery())&&(n.messageTypeInput=e.first)}},inputs:{required:"required",label:"label",placeholder:"placeholder",disabled:"disabled"},features:[t.ɵɵProvidersFeature([{provide:V,useExisting:r((()=>ma)),multi:!0}]),t.ɵɵInheritDefinitionFeature],decls:20,vars:27,consts:[["chipList",""],["messageTypeInput","","origin","matAutocompleteOrigin"],["messageTypeAutocomplete","matAutocomplete"],["searchNotEmpty",""],[2,"width","100%",3,"formGroup"],[4,"ngIf"],[3,"required"],[3,"removable","removed",4,"ngFor","ngForOf"],["matInput","","type","text","formControlName","messageType","matAutocompleteOrigin","",3,"focusin","matChipInputTokenEnd","placeholder","matAutocompleteConnectedTo","matAutocomplete","matChipInputFor","matChipInputSeparatorKeyCodes"],[1,"tb-autocomplete",3,"optionSelected","displayWith"],[3,"value",4,"ngFor","ngForOf"],["class","tb-not-found",3,"value",4,"ngIf"],["aria-hidden","false","aria-label","help-icon","matSuffix","","color","primary",1,"help-icon","margin-8","cursor-pointer",3,"matTooltip"],[3,"removed","removable"],["matChipRemove",""],[3,"value"],[3,"innerHTML"],[1,"tb-not-found",3,"value"],[1,"tb-not-found-content",3,"click"],[4,"ngIf","ngIfElse"],["translate","",3,"click"],["translate",""]],template:function(e,n){if(1&e){const e=t.ɵɵgetCurrentView();t.ɵɵelementStart(0,"mat-form-field",4),t.ɵɵtemplate(1,ra,2,1,"mat-label",5),t.ɵɵelementStart(2,"mat-chip-grid",6,0),t.ɵɵtemplate(4,aa,4,2,"mat-chip-row",7),t.ɵɵelementStart(5,"input",8,1),t.ɵɵpipe(8,"translate"),t.ɵɵlistener("focusin",(function(){return t.ɵɵrestoreView(e),t.ɵɵresetView(n.onFocus())}))("matChipInputTokenEnd",(function(r){return t.ɵɵrestoreView(e),t.ɵɵresetView(n.add(r))})),t.ɵɵelementEnd()(),t.ɵɵelementStart(9,"mat-autocomplete",9,2),t.ɵɵlistener("optionSelected",(function(r){return t.ɵɵrestoreView(e),t.ɵɵresetView(n.selected(r))})),t.ɵɵtemplate(11,ia,3,5,"mat-option",10),t.ɵɵpipe(12,"async"),t.ɵɵtemplate(13,sa,8,3,"mat-option",11),t.ɵɵpipe(14,"async"),t.ɵɵelementEnd(),t.ɵɵelementStart(15,"mat-icon",12),t.ɵɵpipe(16,"translate"),t.ɵɵpipe(17,"translate"),t.ɵɵtext(18,"help"),t.ɵɵelementEnd(),t.ɵɵtemplate(19,pa,3,3,"mat-error",5),t.ɵɵelementEnd()}if(2&e){let e;const r=t.ɵɵreference(3),a=t.ɵɵreference(7),i=t.ɵɵreference(10);t.ɵɵproperty("formGroup",n.messageTypeConfigForm),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.label),t.ɵɵadvance(),t.ɵɵproperty("required",n.required),t.ɵɵadvance(2),t.ɵɵproperty("ngForOf",n.messageTypes),t.ɵɵadvance(),t.ɵɵpropertyInterpolate("placeholder",t.ɵɵpipeBind1(8,14,n.placeholder)),t.ɵɵproperty("matAutocompleteConnectedTo",a)("matAutocomplete",i)("matChipInputFor",r)("matChipInputSeparatorKeyCodes",n.separatorKeysCodes),t.ɵɵadvance(4),t.ɵɵproperty("displayWith",n.displayMessageTypeFn),t.ɵɵadvance(2),t.ɵɵproperty("ngForOf",t.ɵɵpipeBind1(12,16,n.filteredMessageTypes)),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",0===(null==(e=t.ɵɵpipeBind1(14,18,n.filteredMessageTypes))?null:e.length)),t.ɵɵadvance(2),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind2(17,22,"tb.rulenode.chip-help",t.ɵɵpureFunction1(25,ta,t.ɵɵpipeBind1(16,20,"tb.rulenode.message-type")))),t.ɵɵadvance(4),t.ɵɵproperty("ngIf",r.errorState)}},dependencies:t.ɵɵgetComponentDepsFactory(ma),encapsulation:2})}}function da(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",12),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=n.$implicit,r=t.ɵɵnextContext(2);t.ɵɵproperty("value",e)("disabled","cert.PEM"===e&&r.disableCertPemCredentials),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,3,r.credentialsTypeTranslationsMap.get(e))," ")}}function ua(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.credentials-type-required")," "))}function ca(e,t){}function fa(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.username-required")," "))}function ga(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.password-required")," "))}function ha(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-form-field",4)(1,"mat-label",2),t.ɵɵtext(2,"tb.rulenode.username"),t.ɵɵelementEnd(),t.ɵɵelement(3,"input",13),t.ɵɵtemplate(4,fa,3,3,"mat-error",7),t.ɵɵelementEnd(),t.ɵɵelementStart(5,"mat-form-field",4)(6,"mat-label",2),t.ɵɵtext(7,"tb.rulenode.password"),t.ɵɵelementEnd(),t.ɵɵelement(8,"input",14)(9,"tb-toggle-password",15),t.ɵɵtemplate(10,ga,3,3,"mat-error",7),t.ɵɵelementEnd()),2&e){const e=t.ɵɵnextContext(2);t.ɵɵadvance(4),t.ɵɵproperty("ngIf",e.credentialsConfigFormGroup.get("username").hasError("required")),t.ɵɵadvance(4),t.ɵɵproperty("required",e.passwordFieldRequired),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",e.credentialsConfigFormGroup.get("password").hasError("required"))}}function ya(e,n){if(1&e){const e=t.ɵɵgetCurrentView();t.ɵɵelementStart(0,"div",16),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(3,"tb-file-input",17),t.ɵɵpipe(4,"translate"),t.ɵɵpipe(5,"translate"),t.ɵɵlistener("fileNameChanged",(function(n){t.ɵɵrestoreView(e);const r=t.ɵɵnextContext(2);return t.ɵɵresetView(r.credentialsConfigFormGroup.get("caCertFileName").setValue(n))})),t.ɵɵelementEnd(),t.ɵɵelementStart(6,"tb-file-input",18),t.ɵɵpipe(7,"translate"),t.ɵɵpipe(8,"translate"),t.ɵɵlistener("fileNameChanged",(function(n){t.ɵɵrestoreView(e);const r=t.ɵɵnextContext(2);return t.ɵɵresetView(r.credentialsConfigFormGroup.get("certFileName").setValue(n))})),t.ɵɵelementEnd(),t.ɵɵelementStart(9,"tb-file-input",19),t.ɵɵpipe(10,"translate"),t.ɵɵpipe(11,"translate"),t.ɵɵlistener("fileNameChanged",(function(n){t.ɵɵrestoreView(e);const r=t.ɵɵnextContext(2);return t.ɵɵresetView(r.credentialsConfigFormGroup.get("privateKeyFileName").setValue(n))})),t.ɵɵelementEnd(),t.ɵɵelementStart(12,"mat-form-field",4)(13,"mat-label",2),t.ɵɵtext(14,"tb.rulenode.private-key-password"),t.ɵɵelementEnd(),t.ɵɵelement(15,"input",20)(16,"tb-toggle-password",15),t.ɵɵelementEnd()}if(2&e){const e=t.ɵɵnextContext(2);t.ɵɵadvance(),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(2,10,"tb.rulenode.credentials-pem-hint")),t.ɵɵadvance(2),t.ɵɵpropertyInterpolate("label",t.ɵɵpipeBind1(4,12,"tb.rulenode.ca-cert")),t.ɵɵpropertyInterpolate("dropLabel",t.ɵɵpipeBind1(5,14,"tb.rulenode.drop-file")),t.ɵɵproperty("existingFileName",e.credentialsConfigFormGroup.get("caCertFileName").value),t.ɵɵadvance(3),t.ɵɵpropertyInterpolate("label",t.ɵɵpipeBind1(7,16,"tb.rulenode.cert")),t.ɵɵpropertyInterpolate("dropLabel",t.ɵɵpipeBind1(8,18,"tb.rulenode.drop-file")),t.ɵɵproperty("existingFileName",e.credentialsConfigFormGroup.get("certFileName").value),t.ɵɵadvance(3),t.ɵɵpropertyInterpolate("label",t.ɵɵpipeBind1(10,20,"tb.rulenode.private-key")),t.ɵɵpropertyInterpolate("dropLabel",t.ɵɵpipeBind1(11,22,"tb.rulenode.drop-file")),t.ɵɵproperty("existingFileName",e.credentialsConfigFormGroup.get("privateKeyFileName").value)}}function ba(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-form-field",4)(1,"mat-label",2),t.ɵɵtext(2,"tb.rulenode.credentials-type"),t.ɵɵelementEnd(),t.ɵɵelementStart(3,"mat-select",5),t.ɵɵtemplate(4,da,3,5,"mat-option",6),t.ɵɵelementEnd(),t.ɵɵtemplate(5,ua,3,3,"mat-error",7),t.ɵɵelementEnd(),t.ɵɵelementStart(6,"section",8),t.ɵɵtemplate(7,ca,0,0,"ng-template",9)(8,ha,11,3,"ng-template",10)(9,ya,17,24,"ng-template",11),t.ɵɵelementEnd()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(4),t.ɵɵproperty("ngForOf",e.allCredentialsTypes),t.ɵɵadvance(),t.ɵɵproperty("ngIf",e.credentialsConfigFormGroup.get("type").hasError("required")),t.ɵɵadvance(),t.ɵɵproperty("ngSwitch",e.credentialsConfigFormGroup.get("type").value)}}e("MessageTypesConfigComponent",ma);class va extends y{get required(){return this.requiredValue}set required(e){this.requiredValue=Z(e)}constructor(e,t){super(e),this.store=e,this.fb=t,this.subscriptions=[],this.disableCertPemCredentials=!1,this.passwordFieldRequired=!0,this.allCredentialsTypes=Mt,this.credentialsTypeTranslationsMap=Bt,this.propagateChange=e=>{}}ngOnInit(){this.credentialsConfigFormGroup=this.fb.group({type:[null,[N.required]],username:[null,[]],password:[null,[]],caCert:[null,[]],caCertFileName:[null,[]],privateKey:[null,[]],privateKeyFileName:[null,[]],cert:[null,[]],certFileName:[null,[]]}),this.subscriptions.push(this.credentialsConfigFormGroup.valueChanges.subscribe((()=>{this.updateView()}))),this.subscriptions.push(this.credentialsConfigFormGroup.get("type").valueChanges.subscribe((()=>{this.credentialsTypeChanged()})))}ngOnChanges(e){for(const t of Object.keys(e)){const n=e[t];if(!n.firstChange&&n.currentValue!==n.previousValue&&n.currentValue&&"disableCertPemCredentials"===t){"cert.PEM"===this.credentialsConfigFormGroup.get("type").value&&setTimeout((()=>{this.credentialsConfigFormGroup.get("type").patchValue("anonymous",{emitEvent:!0})}))}}}ngOnDestroy(){this.subscriptions.forEach((e=>e.unsubscribe()))}writeValue(e){P(e)&&(this.credentialsConfigFormGroup.reset(e,{emitEvent:!1}),this.updateValidators())}setDisabledState(e){e?this.credentialsConfigFormGroup.disable({emitEvent:!1}):(this.credentialsConfigFormGroup.enable({emitEvent:!1}),this.updateValidators())}updateView(){let e=this.credentialsConfigFormGroup.value;const t=e.type;switch(t){case"anonymous":e={type:t};break;case"basic":e={type:t,username:e.username,password:e.password};break;case"cert.PEM":delete e.username}this.propagateChange(e)}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}validate(e){return this.credentialsConfigFormGroup.valid?null:{credentialsConfig:{valid:!1}}}credentialsTypeChanged(){this.credentialsConfigFormGroup.patchValue({username:null,password:null,caCert:null,caCertFileName:null,privateKey:null,privateKeyFileName:null,cert:null,certFileName:null}),this.updateValidators()}updateValidators(e=!1){const t=this.credentialsConfigFormGroup.get("type").value;switch(e&&this.credentialsConfigFormGroup.reset({type:t},{emitEvent:!1}),this.credentialsConfigFormGroup.setValidators([]),this.credentialsConfigFormGroup.get("username").setValidators([]),this.credentialsConfigFormGroup.get("password").setValidators([]),t){case"anonymous":break;case"basic":this.credentialsConfigFormGroup.get("username").setValidators([N.required]),this.credentialsConfigFormGroup.get("password").setValidators(this.passwordFieldRequired?[N.required]:[]);break;case"cert.PEM":this.credentialsConfigFormGroup.setValidators([this.requiredFilesSelected(N.required,[["caCert","caCertFileName"],["privateKey","privateKeyFileName","cert","certFileName"]])])}this.credentialsConfigFormGroup.get("username").updateValueAndValidity({emitEvent:e}),this.credentialsConfigFormGroup.get("password").updateValueAndValidity({emitEvent:e}),this.credentialsConfigFormGroup.updateValueAndValidity({emitEvent:e})}requiredFilesSelected(e,t=null){return n=>{t||(t=[Object.keys(n.controls)]);return n?.controls&&t.some((t=>t.every((t=>!e(n.controls[t])))))?null:{notAllRequiredFilesSelected:!0}}}static{this.ɵfac=function(e){return new(e||va)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.FormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:va,selectors:[["tb-credentials-config"]],inputs:{required:"required",disableCertPemCredentials:"disableCertPemCredentials",passwordFieldRequired:"passwordFieldRequired"},features:[t.ɵɵProvidersFeature([{provide:V,useExisting:r((()=>va)),multi:!0},{provide:O,useExisting:r((()=>va)),multi:!0}]),t.ɵɵInheritDefinitionFeature,t.ɵɵNgOnChangesFeature],decls:9,vars:4,consts:[[1,"flex","flex-col",3,"formGroup"],[1,"tb-credentials-config-panel-group"],["translate",""],["matExpansionPanelContent",""],[1,"mat-block"],["formControlName","type","required",""],[3,"value","disabled",4,"ngFor","ngForOf"],[4,"ngIf"],[1,"flex","flex-col",3,"ngSwitch"],["ngSwitchCase","anonymous"],["ngSwitchCase","basic"],["ngSwitchCase","cert.PEM"],[3,"value","disabled"],["required","","matInput","","formControlName","username"],["type","password","matInput","","formControlName","password",3,"required"],["matSuffix",""],[1,"tb-hint"],["formControlName","caCert","inputId","caCertSelect","noFileText","tb.rulenode.no-file",3,"fileNameChanged","existingFileName","label","dropLabel"],["formControlName","cert","inputId","CertSelect","noFileText","tb.rulenode.no-file",3,"fileNameChanged","existingFileName","label","dropLabel"],["formControlName","privateKey","inputId","privateKeySelect","noFileText","tb.rulenode.no-file",2,"padding-bottom","8px",3,"fileNameChanged","existingFileName","label","dropLabel"],["type","password","matInput","","formControlName","password"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"mat-expansion-panel",1)(2,"mat-expansion-panel-header")(3,"mat-panel-title",2),t.ɵɵtext(4,"tb.rulenode.credentials"),t.ɵɵelementEnd(),t.ɵɵelementStart(5,"mat-panel-description"),t.ɵɵtext(6),t.ɵɵpipe(7,"translate"),t.ɵɵelementEnd()(),t.ɵɵtemplate(8,ba,10,3,"ng-template",3),t.ɵɵelementEnd()()),2&e&&(t.ɵɵproperty("formGroup",n.credentialsConfigFormGroup),t.ɵɵadvance(6),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(7,2,n.credentialsTypeTranslationsMap.get(n.credentialsConfigFormGroup.get("type").value))," "))},dependencies:t.ɵɵgetComponentDepsFactory(va),encapsulation:2})}}function xa(e,n){1&e&&(t.ɵɵelementStart(0,"button",22),t.ɵɵpipe(1,"translate"),t.ɵɵelementStart(2,"mat-icon"),t.ɵɵtext(3,"drag_handle"),t.ɵɵelementEnd()()),2&e&&t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(1,1,"action.drag"))}function Ca(e,n){if(1&e&&(t.ɵɵelementStart(0,"span",23),t.ɵɵtext(1),t.ɵɵelementEnd()),2&e){const e=t.ɵɵnextContext().$implicit;t.ɵɵadvance(),t.ɵɵtextInterpolate1("",e.get("name").value,".")}}function Sa(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",24),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementStart(3,"small",25),t.ɵɵtext(4),t.ɵɵelementEnd()()),2&e){const e=n.$implicit,r=t.ɵɵnextContext(2);t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,3,r.argumentTypeMap.get(e).name)," "),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",r.argumentTypeMap.get(e).description," ")}}function Ta(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error",13),t.ɵɵtext(1," tb.rulenode.argument-source-field-input-required "),t.ɵɵelementEnd())}function Ia(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error",13),t.ɵɵtext(1," tb.rulenode.argument-key-field-input-required "),t.ɵɵelementEnd())}function Ea(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-form-field",26)(1,"mat-label",13),t.ɵɵtext(2,"tb.rulenode.argument-key-field-input"),t.ɵɵelementEnd(),t.ɵɵelement(3,"input",27),t.ɵɵelementStart(4,"mat-icon",28),t.ɵɵpipe(5,"translate"),t.ɵɵtext(6," help "),t.ɵɵelementEnd(),t.ɵɵtemplate(7,Ia,2,0,"mat-error",16),t.ɵɵelementEnd()),2&e){const e=t.ɵɵnextContext().$implicit;t.ɵɵadvance(3),t.ɵɵproperty("formControl",e.get("key")),t.ɵɵadvance(),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(5,3,"tb.rulenode.math-templatization-tooltip")),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",e.get("key").hasError("required"))}}function Fa(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error",13),t.ɵɵtext(1," tb.rulenode.constant-value-field-input-required "),t.ɵɵelementEnd())}function qa(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-form-field",29)(1,"mat-label",13),t.ɵɵtext(2,"tb.rulenode.constant-value-field-input"),t.ɵɵelementEnd(),t.ɵɵelement(3,"input",30),t.ɵɵtemplate(4,Fa,2,0,"mat-error",16),t.ɵɵelementEnd()),2&e){const e=t.ɵɵnextContext().$implicit;t.ɵɵadvance(3),t.ɵɵproperty("formControl",e.get("key")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",e.get("key").hasError("required"))}}function Aa(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-form-field",26)(1,"mat-label",13),t.ɵɵtext(2,"tb.rulenode.default-value-field-input"),t.ɵɵelementEnd(),t.ɵɵelement(3,"input",31),t.ɵɵelementEnd()),2&e){const e=t.ɵɵnextContext().$implicit;t.ɵɵadvance(3),t.ɵɵproperty("formControl",e.get("defaultValue"))}}function ka(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",33),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=n.$implicit,r=t.ɵɵnextContext(3);t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,2,r.attributeScopeMap.get(e))," ")}}function Na(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error",13),t.ɵɵtext(1," tb.rulenode.attribute-scope-field-input-required "),t.ɵɵelementEnd())}function wa(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-form-field",12)(1,"mat-label",13),t.ɵɵtext(2,"tb.rulenode.attribute-scope-field-input"),t.ɵɵelementEnd(),t.ɵɵelementStart(3,"mat-select",14),t.ɵɵtemplate(4,ka,3,4,"mat-option",32),t.ɵɵelementEnd(),t.ɵɵtemplate(5,Na,2,0,"mat-error",16),t.ɵɵelementEnd()),2&e){const e=t.ɵɵnextContext().$implicit,n=t.ɵɵnextContext();t.ɵɵadvance(3),t.ɵɵproperty("formControl",e.get("attributeScope")),t.ɵɵadvance(),t.ɵɵproperty("ngForOf",n.attributeScope),t.ɵɵadvance(),t.ɵɵproperty("ngIf",e.get("attributeScope").hasError("required"))}}function Ma(e,n){if(1&e){const e=t.ɵɵgetCurrentView();t.ɵɵelementStart(0,"button",34),t.ɵɵpipe(1,"translate"),t.ɵɵlistener("click",(function(){t.ɵɵrestoreView(e);const n=t.ɵɵnextContext().index,r=t.ɵɵnextContext();return t.ɵɵresetView(r.removeArgument(n))})),t.ɵɵelementStart(2,"mat-icon"),t.ɵɵtext(3,"close"),t.ɵɵelementEnd()()}2&e&&t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(1,1,"action.remove"))}function Ba(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-list-item",6)(1,"div",7),t.ɵɵtemplate(2,xa,4,3,"button",8),t.ɵɵelementStart(3,"div",9),t.ɵɵtemplate(4,Ca,2,1,"span",10),t.ɵɵelementStart(5,"div",11)(6,"mat-form-field",12)(7,"mat-label",13),t.ɵɵtext(8,"tb.rulenode.argument-source-field-input"),t.ɵɵelementEnd(),t.ɵɵelementStart(9,"mat-select",14)(10,"mat-select-trigger"),t.ɵɵtext(11),t.ɵɵpipe(12,"translate"),t.ɵɵelementEnd(),t.ɵɵtemplate(13,Sa,5,5,"mat-option",15),t.ɵɵelementEnd(),t.ɵɵtemplate(14,Ta,2,0,"mat-error",16),t.ɵɵelementEnd(),t.ɵɵelementStart(15,"div",17),t.ɵɵtemplate(16,Ea,8,5,"mat-form-field",18)(17,qa,5,2,"mat-form-field",19)(18,Aa,4,1,"mat-form-field",18),t.ɵɵelementEnd(),t.ɵɵtemplate(19,wa,6,3,"mat-form-field",20),t.ɵɵelementEnd(),t.ɵɵtemplate(20,Ma,4,3,"button",21),t.ɵɵelementEnd()()()),2&e){let e;const r=n.$implicit,a=t.ɵɵnextContext();t.ɵɵproperty("cdkDragDisabled",a.disabled),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",!a.disabled),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",a.displayArgumentName),t.ɵɵadvance(5),t.ɵɵproperty("formControl",r.get("type")),t.ɵɵadvance(2),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(12,12,null==(e=a.argumentTypeMap.get(r.get("type").value))?null:e.name)," "),t.ɵɵadvance(2),t.ɵɵproperty("ngForOf",a.arguments),t.ɵɵadvance(),t.ɵɵproperty("ngIf",r.get("type").hasError("required")),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",r.get("type").value&&r.get("type").value!==a.ArgumentType.CONSTANT),t.ɵɵadvance(),t.ɵɵproperty("ngIf",r.get("type").value===a.ArgumentType.CONSTANT),t.ɵɵadvance(),t.ɵɵproperty("ngIf",r.get("type").value&&r.get("type").value!==a.ArgumentType.CONSTANT),t.ɵɵadvance(),t.ɵɵproperty("ngIf",r.get("type").value===a.ArgumentType.ATTRIBUTE),t.ɵɵadvance(),t.ɵɵproperty("ngIf",!a.disabled)}}function Va(e,n){1&e&&(t.ɵɵelementStart(0,"div")(1,"span",35),t.ɵɵtext(2,"tb.rulenode.no-arguments-prompt"),t.ɵɵelementEnd()())}e("CredentialsConfigComponent",va);class Oa extends y{get function(){return this.functionValue}set function(e){e&&this.functionValue!==e&&(this.functionValue=e,this.setupArgumentsFormGroup(!0))}constructor(e,t){super(e),this.store=e,this.fb=t,this.maxArgs=16,this.minArgs=1,this.displayArgumentName=!1,this.mathFunctionMap=_t,this.ArgumentType=jt,this.attributeScopeMap=Xt,this.argumentTypeMap=$t,this.arguments=Object.values(jt),this.attributeScope=Object.values(Yt),this.propagateChange=null,this.valueChangeSubscription=[]}ngOnInit(){this.argumentsFormGroup=this.fb.group({arguments:this.fb.array([])}),this.valueChangeSubscription.push(this.argumentsFormGroup.valueChanges.subscribe((()=>{this.updateModel()}))),this.setupArgumentsFormGroup()}onDrop(e){const t=this.argumentsFormArray,n=t.at(e.previousIndex);t.removeAt(e.previousIndex),t.insert(e.currentIndex,n),this.updateArgumentNames()}get argumentsFormArray(){return this.argumentsFormGroup.get("arguments")}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.argumentsFormGroup.disable({emitEvent:!1}):(this.argumentsFormGroup.enable({emitEvent:!1}),this.argumentsFormArray.controls.forEach((e=>this.updateArgumentControlValidators(e))))}ngOnDestroy(){this.valueChangeSubscription.length&&this.valueChangeSubscription.forEach((e=>e.unsubscribe()))}writeValue(e){const t=[];e&&e.forEach(((e,n)=>{t.push(this.createArgumentControl(e,n))})),this.argumentsFormGroup.setControl("arguments",this.fb.array(t),{emitEvent:!1}),this.setupArgumentsFormGroup()}removeArgument(e){this.argumentsFormArray.removeAt(e),this.updateArgumentNames()}addArgument(e=!0){const t=this.argumentsFormArray,n=this.createArgumentControl(null,t.length);t.push(n,{emitEvent:e})}validate(e){return this.argumentsFormGroup.valid?null:{argumentsRequired:!0}}setupArgumentsFormGroup(e=!1){if(this.function&&(this.maxArgs=this.mathFunctionMap.get(this.function).maxArgs,this.minArgs=this.mathFunctionMap.get(this.function).minArgs,this.displayArgumentName=this.function===Rt.CUSTOM),this.argumentsFormGroup){for(this.argumentsFormGroup.get("arguments").setValidators([N.minLength(this.minArgs),N.maxLength(this.maxArgs)]);this.argumentsFormArray.length>this.maxArgs;)this.removeArgument(this.maxArgs-1);for(;this.argumentsFormArray.length{this.updateArgumentControlValidators(n),n.get("attributeScope").updateValueAndValidity({emitEvent:!1}),n.get("defaultValue").updateValueAndValidity({emitEvent:!1})}))),n}updateArgumentControlValidators(e){const t=e.get("type").value;t===jt.ATTRIBUTE?e.get("attributeScope").enable({emitEvent:!1}):e.get("attributeScope").disable({emitEvent:!1}),t&&t!==jt.CONSTANT?e.get("defaultValue").enable({emitEvent:!1}):e.get("defaultValue").disable({emitEvent:!1})}updateArgumentNames(){this.argumentsFormArray.controls.forEach(((e,t)=>{e.get("name").setValue(Jt[t])}))}updateModel(){const e=this.argumentsFormArray.value;e.length&&this.argumentsFormGroup.valid?this.propagateChange(e):this.propagateChange(null)}static{this.ɵfac=function(e){return new(e||Oa)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.FormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Oa,selectors:[["tb-arguments-map-config"]],inputs:{disabled:"disabled",function:"function"},features:[t.ɵɵProvidersFeature([{provide:V,useExisting:r((()=>Oa)),multi:!0},{provide:O,useExisting:r((()=>Oa)),multi:!0}]),t.ɵɵInheritDefinitionFeature],decls:10,vars:10,consts:[[1,"flex","flex-col"],[2,"max-height","500px","overflow","auto"],["cdkDropList","","cdkDropListOrientation","vertical",1,"tb-drop-list","arguments-list",3,"cdkDropListDropped","formGroup","cdkDropListDisabled"],["formArrayName","arguments","cdkDrag","","class","tb-argument tb-draggable","style","height: 100%",3,"cdkDragDisabled",4,"ngFor","ngForOf"],[4,"ngIf"],["mat-button","","mat-raised-button","","color","primary","type","button","matTooltipPosition","above",3,"click","disabled"],["formArrayName","arguments","cdkDrag","",1,"tb-argument","tb-draggable",2,"height","100%",3,"cdkDragDisabled"],[1,"flex","flex-1","flex-row","items-center","justify-start"],["mat-icon-button","","color","primary","cdkDragHandle","","class","tb-drag-handle handle","style","min-width: 40px; margin: 0","matTooltipPosition","above",3,"matTooltip",4,"ngIf"],[1,"flex","flex-1","flex-row","items-center","justify-start","gap-4"],["style","padding: 0 10px; min-width: 20px;",4,"ngIf"],[1,"flex","flex-1","flex-col"],[1,"mat-block"],["translate",""],["required","",3,"formControl"],["style","border-bottom: 1px solid #eee;",3,"value",4,"ngFor","ngForOf"],["translate","",4,"ngIf"],[1,"flex","flex-1","flex-row","xs:flex-col","gt-xs:gap-4"],["floatLabel","always","class","mat-block gt-xs:max-w-50% gt-xs:flex-full",4,"ngIf"],["floatLabel","always","class","mat-block flex-1",4,"ngIf"],["class","mat-block",4,"ngIf"],["mat-icon-button","","color","primary","style","min-width: 40px;","matTooltipPosition","above",3,"matTooltip","click",4,"ngIf"],["mat-icon-button","","color","primary","cdkDragHandle","","matTooltipPosition","above",1,"tb-drag-handle","handle",2,"min-width","40px","margin","0",3,"matTooltip"],[2,"padding","0 10px","min-width","20px"],[2,"border-bottom","1px solid #eee",3,"value"],[2,"display","block","overflow","hidden","text-overflow","ellipsis","white-space","nowrap"],["floatLabel","always",1,"mat-block","gt-xs:max-w-50%","gt-xs:flex-full"],["matInput","","required","",3,"formControl"],["aria-hidden","false","aria-label","help-icon","matSuffix","","color","primary",1,"help-icon","margin-8","cursor-pointer",3,"matTooltip"],["floatLabel","always",1,"mat-block","flex-1"],["matInput","","required","","step","1","min","0","type","number",3,"formControl"],["matInput","","step","1","type","number",3,"formControl"],[3,"value",4,"ngFor","ngForOf"],[3,"value"],["mat-icon-button","","color","primary","matTooltipPosition","above",2,"min-width","40px",3,"click","matTooltip"],["translate","",1,"tb-prompt","flex","items-center","justify-center"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"div",1)(2,"mat-list",2),t.ɵɵlistener("cdkDropListDropped",(function(e){return n.onDrop(e)})),t.ɵɵtemplate(3,Ba,21,14,"mat-list-item",3),t.ɵɵelementEnd()(),t.ɵɵtemplate(4,Va,3,0,"div",4),t.ɵɵelementStart(5,"button",5),t.ɵɵlistener("click",(function(){return n.addArgument()})),t.ɵɵelementStart(6,"mat-icon"),t.ɵɵtext(7,"add"),t.ɵɵelementEnd(),t.ɵɵtext(8),t.ɵɵpipe(9,"translate"),t.ɵɵelementEnd()()),2&e&&(t.ɵɵadvance(),t.ɵɵclassProp("readonly",n.disabled),t.ɵɵadvance(),t.ɵɵproperty("formGroup",n.argumentsFormGroup)("cdkDropListDisabled",n.disabled),t.ɵɵadvance(),t.ɵɵproperty("ngForOf",n.argumentsFormArray.controls),t.ɵɵadvance(),t.ɵɵproperty("ngIf",!n.argumentsFormArray.length),t.ɵɵadvance(),t.ɵɵproperty("disabled",n.argumentsFormArray.length>=n.maxArgs),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(9,8,"action.add")," "))},dependencies:t.ɵɵgetComponentDepsFactory(Oa),styles:["[_nghost-%COMP%] .mat-mdc-list-item.tb-argument[_ngcontent-%COMP%]{border:solid rgba(0,0,0,.25) 1px;border-radius:4px;padding:10px 0;margin-bottom:16px}[_nghost-%COMP%] .arguments-list[_ngcontent-%COMP%]{padding:0}"]})}}e("ArgumentsMapConfigComponent",Oa);const Da=["operationInput"];function La(e,n){if(1&e){const e=t.ɵɵgetCurrentView();t.ɵɵelementStart(0,"button",9),t.ɵɵlistener("click",(function(){t.ɵɵrestoreView(e);const n=t.ɵɵnextContext();return t.ɵɵresetView(n.clear())})),t.ɵɵelementStart(1,"mat-icon",10),t.ɵɵtext(2,"close"),t.ɵɵelementEnd()()}}function Pa(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",11),t.ɵɵelement(1,"span",12),t.ɵɵpipe(2,"highlight"),t.ɵɵelementStart(3,"small",13),t.ɵɵtext(4),t.ɵɵelementEnd()()),2&e){const e=n.$implicit,r=t.ɵɵnextContext();t.ɵɵproperty("value",e.value),t.ɵɵadvance(),t.ɵɵproperty("innerHTML",t.ɵɵpipeBind2(2,3,e.value+" | "+e.name,r.searchText),t.ɵɵsanitizeHtml),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",e.description," ")}}function Ra(e,n){1&e&&(t.ɵɵelementStart(0,"mat-option",11)(1,"span",3),t.ɵɵtext(2,"tb.rulenode.no-option-found"),t.ɵɵelementEnd()()),2&e&&t.ɵɵproperty("value",null)}class _a extends y{get required(){return this.requiredValue}set required(e){this.requiredValue=Z(e)}constructor(e,t,n,r){super(e),this.store=e,this.translate=t,this.injector=n,this.fb=r,this.searchText="",this.dirty=!1,this.mathOperation=[..._t.values()],this.propagateChange=null}ngOnInit(){this.mathFunctionForm=this.fb.group({operation:[""]}),this.filteredOptions=this.mathFunctionForm.get("operation").valueChanges.pipe(ae((e=>{let t;t="string"==typeof e&&Rt[e]?Rt[e]:null,this.updateView(t)})),te((e=>(this.searchText=e||"",e?this._filter(e):this.mathOperation.slice()))))}_filter(e){const t=e.toLowerCase();return this.mathOperation.filter((e=>e.name.toLowerCase().includes(t)||e.value.toLowerCase().includes(t)))}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.mathFunctionForm.disable({emitEvent:!1}):this.mathFunctionForm.enable({emitEvent:!1})}mathFunctionDisplayFn(e){if(e){const t=_t.get(e);return t.value+" | "+t.name}return""}writeValue(e){this.modelValue=e,this.mathFunctionForm.get("operation").setValue(e,{emitEvent:!1}),this.dirty=!0}updateView(e){this.modelValue!==e&&(this.modelValue=e,this.propagateChange(this.modelValue))}onFocus(){this.dirty&&(this.mathFunctionForm.get("operation").updateValueAndValidity({onlySelf:!0}),this.dirty=!1)}clear(){this.mathFunctionForm.get("operation").patchValue(""),setTimeout((()=>{this.operationInput.nativeElement.blur(),this.operationInput.nativeElement.focus()}),0)}static{this.ɵfac=function(e){return new(e||_a)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(K.TranslateService),t.ɵɵdirectiveInject(t.Injector),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:_a,selectors:[["tb-math-function-autocomplete"]],viewQuery:function(e,n){if(1&e&&t.ɵɵviewQuery(Da,7),2&e){let e;t.ɵɵqueryRefresh(e=t.ɵɵloadQuery())&&(n.operationInput=e.first)}},inputs:{required:"required",disabled:"disabled"},features:[t.ɵɵProvidersFeature([{provide:V,useExisting:r((()=>_a)),multi:!0}]),t.ɵɵInheritDefinitionFeature],decls:12,vars:11,consts:[["operationInput",""],["auto","matAutocomplete"],[1,"mat-block",3,"formGroup"],["translate",""],["type","text","matInput","","formControlName","operation",3,"focusin","required","matAutocomplete"],["type","button","matSuffix","","mat-icon-button","","aria-label","Clear",3,"click",4,"ngIf"],[1,"tb-autocomplete",3,"displayWith"],[3,"value",4,"ngFor","ngForOf"],[3,"value",4,"ngIf"],["type","button","matSuffix","","mat-icon-button","","aria-label","Clear",3,"click"],[1,"material-icons"],[3,"value"],[3,"innerHTML"],[2,"display","block","overflow","hidden","text-overflow","ellipsis","white-space","nowrap"]],template:function(e,n){if(1&e){const e=t.ɵɵgetCurrentView();t.ɵɵelementStart(0,"mat-form-field",2)(1,"mat-label",3),t.ɵɵtext(2,"tb.rulenode.functions-field-input"),t.ɵɵelementEnd(),t.ɵɵelementStart(3,"input",4,0),t.ɵɵlistener("focusin",(function(){return t.ɵɵrestoreView(e),t.ɵɵresetView(n.onFocus())})),t.ɵɵelementEnd(),t.ɵɵtemplate(5,La,3,0,"button",5),t.ɵɵelementStart(6,"mat-autocomplete",6,1),t.ɵɵtemplate(8,Pa,5,6,"mat-option",7),t.ɵɵpipe(9,"async"),t.ɵɵtemplate(10,Ra,3,1,"mat-option",8),t.ɵɵpipe(11,"async"),t.ɵɵelementEnd()()}if(2&e){let e;const r=t.ɵɵreference(7);t.ɵɵproperty("formGroup",n.mathFunctionForm),t.ɵɵadvance(3),t.ɵɵproperty("required",n.required)("matAutocomplete",r),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",n.mathFunctionForm.get("operation").value),t.ɵɵadvance(),t.ɵɵproperty("displayWith",n.mathFunctionDisplayFn),t.ɵɵadvance(2),t.ɵɵproperty("ngForOf",t.ɵɵpipeBind1(9,7,n.filteredOptions)),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",!(null!=(e=t.ɵɵpipeBind1(11,9,n.filteredOptions))&&e.length))}},dependencies:t.ɵɵgetComponentDepsFactory(_a),encapsulation:2})}}function ja(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",8),t.ɵɵtext(1),t.ɵɵelementEnd()),2&e){const e=n.$implicit;t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",e.name," ")}}function Ga(e,n){if(1&e&&(t.ɵɵelementStart(0,"button",9),t.ɵɵpipe(1,"translate"),t.ɵɵelementStart(2,"mat-icon",10),t.ɵɵtext(3,"content_copy "),t.ɵɵelementEnd()()),2&e){const e=t.ɵɵnextContext();t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(1,2,"tb.rulenode.copy-message-type")),t.ɵɵproperty("cbContent",e.messageTypeFormGroup.get("messageType").value)}}function Ka(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.message-type-value-required")," "))}function Ua(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.message-type-value-max-length")," "))}e("MathFunctionAutocompleteComponent",_a);class Ha{set required(e){this.requiredValue!==e&&(this.requiredValue=e,this.updateValidators())}get required(){return this.requiredValue}constructor(e){this.fb=e,this.subscriptSizing="fixed",this.messageTypes=[{name:"Post attributes",value:"POST_ATTRIBUTES_REQUEST"},{name:"Post telemetry",value:"POST_TELEMETRY_REQUEST"},{name:"Custom",value:""}],this.propagateChange=()=>{},this.destroy$=new Y,this.messageTypeFormGroup=this.fb.group({messageTypeAlias:[null,[N.required]],messageType:[{value:null,disabled:!0},[N.maxLength(255)]]}),this.messageTypeFormGroup.get("messageTypeAlias").valueChanges.pipe(W(this.destroy$)).subscribe((e=>this.updateMessageTypeValue(e))),this.messageTypeFormGroup.valueChanges.pipe(W(this.destroy$)).subscribe((()=>this.updateView()))}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}registerOnTouched(e){}registerOnChange(e){this.propagateChange=e}writeValue(e){this.modelValue=e;let t=this.messageTypes.find((t=>t.value===e));t||(t=this.messageTypes.find((e=>""===e.value))),this.messageTypeFormGroup.get("messageTypeAlias").patchValue(t,{emitEvent:!1}),this.messageTypeFormGroup.get("messageType").patchValue(e,{emitEvent:!1})}validate(){return this.messageTypeFormGroup.valid?null:{messageTypeInvalid:!0}}setDisabledState(e){this.disabled=e,e?this.messageTypeFormGroup.disable({emitEvent:!1}):(this.messageTypeFormGroup.enable({emitEvent:!1}),"Custom"!==this.messageTypeFormGroup.get("messageTypeAlias").value?.name&&this.messageTypeFormGroup.get("messageType").disable({emitEvent:!1}))}updateView(){const e=this.messageTypeFormGroup.getRawValue().messageType;this.modelValue!==e&&(this.modelValue=e,this.propagateChange(this.modelValue))}updateValidators(){this.messageTypeFormGroup.get("messageType").setValidators(this.required?[N.required,N.maxLength(255)]:[N.maxLength(255)]),this.messageTypeFormGroup.get("messageType").updateValueAndValidity({emitEvent:!1})}updateMessageTypeValue(e){"Custom"!==e?.name?this.messageTypeFormGroup.get("messageType").disable({emitEvent:!1}):this.messageTypeFormGroup.get("messageType").enable({emitEvent:!1}),this.messageTypeFormGroup.get("messageType").patchValue(e.value??null)}static{this.ɵfac=function(e){return new(e||Ha)(t.ɵɵdirectiveInject(k.FormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Ha,selectors:[["tb-output-message-type-autocomplete"]],inputs:{subscriptSizing:"subscriptSizing",disabled:"disabled",required:"required"},features:[t.ɵɵProvidersFeature([{provide:V,useExisting:r((()=>Ha)),multi:!0},{provide:O,useExisting:r((()=>Ha)),multi:!0}])],decls:15,vars:14,consts:[[1,"tb-form-row","no-border","no-padding","tb-standard-fields","column-xs",3,"formGroup"],["hideRequiredMarker","",1,"flex",3,"subscriptSizing"],["formControlName","messageTypeAlias"],[3,"value",4,"ngFor","ngForOf"],[1,"flex",3,"subscriptSizing","hideRequiredMarker"],["matInput","","type","text","formControlName","messageType"],["type","button","matSuffix","","mat-icon-button","","aria-label","Copy","ngxClipboard","",3,"cbContent","matTooltip",4,"ngIf"],[4,"ngIf"],[3,"value"],["type","button","matSuffix","","mat-icon-button","","aria-label","Copy","ngxClipboard","",3,"cbContent","matTooltip"],["aria-hidden","false","aria-label","help-icon"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"mat-form-field",1)(2,"mat-label"),t.ɵɵtext(3),t.ɵɵpipe(4,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(5,"mat-select",2),t.ɵɵtemplate(6,ja,2,2,"mat-option",3),t.ɵɵelementEnd()(),t.ɵɵelementStart(7,"mat-form-field",4)(8,"mat-label"),t.ɵɵtext(9),t.ɵɵpipe(10,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(11,"input",5),t.ɵɵtemplate(12,Ga,4,4,"button",6)(13,Ka,3,3,"mat-error",7)(14,Ua,3,3,"mat-error",7),t.ɵɵelementEnd()()),2&e&&(t.ɵɵproperty("formGroup",n.messageTypeFormGroup),t.ɵɵadvance(),t.ɵɵpropertyInterpolate("subscriptSizing",n.subscriptSizing),t.ɵɵadvance(2),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(4,10,"tb.rulenode.output-message-type")),t.ɵɵadvance(3),t.ɵɵproperty("ngForOf",n.messageTypes),t.ɵɵadvance(),t.ɵɵpropertyInterpolate("subscriptSizing",n.subscriptSizing),t.ɵɵproperty("hideRequiredMarker",n.messageTypeFormGroup.get("messageType").disabled),t.ɵɵadvance(2),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(10,12,"tb.rulenode.message-type-value")),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",n.messageTypeFormGroup.get("messageType").value),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.messageTypeFormGroup.get("messageType").hasError("required")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.messageTypeFormGroup.get("messageType").hasError("maxlength")))},dependencies:t.ɵɵgetComponentDepsFactory(Ha),encapsulation:2})}}e("OutputMessageTypeAutocompleteComponent",Ha),J([h()],Ha.prototype,"disabled",void 0),J([h()],Ha.prototype,"required",null);const za=(e,t)=>({keyText:e,valText:t});function $a(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=t.ɵɵnextContext(2);t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,e.keyRequiredText)," ")}}function Qa(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=t.ɵɵnextContext(2);t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,e.valRequiredText)," ")}}function Ja(e,n){if(1&e){const e=t.ɵɵgetCurrentView();t.ɵɵelementStart(0,"div",10)(1,"mat-form-field",11),t.ɵɵelement(2,"input",12),t.ɵɵpipe(3,"translate"),t.ɵɵtemplate(4,$a,3,3,"mat-error",13),t.ɵɵelementEnd(),t.ɵɵelementStart(5,"mat-form-field",11),t.ɵɵelement(6,"input",12),t.ɵɵpipe(7,"translate"),t.ɵɵtemplate(8,Qa,3,3,"mat-error",13),t.ɵɵelementEnd(),t.ɵɵelementStart(9,"button",14),t.ɵɵpipe(10,"translate"),t.ɵɵpipe(11,"async"),t.ɵɵlistener("click",(function(){const n=t.ɵɵrestoreView(e).index,r=t.ɵɵnextContext();return t.ɵɵresetView(r.removeKeyVal(n))})),t.ɵɵelementStart(12,"mat-icon"),t.ɵɵtext(13,"close"),t.ɵɵelementEnd()()()}if(2&e){const e=n.$implicit,r=t.ɵɵnextContext();t.ɵɵadvance(2),t.ɵɵpropertyInterpolate("placeholder",t.ɵɵpipeBind1(3,10,r.keyText)),t.ɵɵproperty("formControl",e.get("key")),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",e.get("key").hasError("required")),t.ɵɵadvance(2),t.ɵɵpropertyInterpolate("placeholder",t.ɵɵpipeBind1(7,12,r.valText)),t.ɵɵproperty("formControl",e.get("value")),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",e.get("value").hasError("required")),t.ɵɵadvance(),t.ɵɵclassProp("!hidden",r.disabled),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(10,14,"tb.key-val.remove-entry")),t.ɵɵproperty("disabled",t.ɵɵpipeBind1(11,16,r.isLoading$))}}function Ya(e,n){if(1&e&&(t.ɵɵelement(0,"div",15),t.ɵɵpipe(1,"translate"),t.ɵɵpipe(2,"safe")),2&e){const e=t.ɵɵnextContext();t.ɵɵproperty("innerHTML",t.ɵɵpipeBind2(2,3,t.ɵɵpipeBind1(1,1,e.hintText),"html"),t.ɵɵsanitizeHtml)}}class Wa extends y{get required(){return this.requiredValue}set required(e){this.requiredValue=Z(e)}constructor(e,t,n,r){super(e),this.store=e,this.translate=t,this.injector=n,this.fb=r,this.propagateChange=null,this.valueChangeSubscription=null}ngOnInit(){this.ngControl=this.injector.get(B),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))}keyValsFormArray(){return this.kvListFormGroup.get("keyVals")}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})}writeValue(e){this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();const t=[];if(e)for(const n of Object.keys(e))Object.prototype.hasOwnProperty.call(e,n)&&t.push(this.fb.group({key:[n,[N.required]],value:[e[n],[N.required]]}));this.kvListFormGroup.setControl("keyVals",this.fb.array(t)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((()=>{this.updateModel()}))}removeKeyVal(e){this.kvListFormGroup.get("keyVals").removeAt(e)}addKeyVal(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[N.required]],value:["",[N.required]]}))}validate(e){const t=this.kvListFormGroup.get("keyVals").value;if(!t.length&&this.required)return{kvMapRequired:!0};if(!this.kvListFormGroup.valid)return{kvFieldsRequired:!0};if(this.uniqueKeyValuePairValidator)for(const e of t)if(e.key===e.value)return{uniqueKeyValuePair:!0};return null}updateModel(){const e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{const t={};e.forEach((e=>{t[e.key]=e.value})),this.propagateChange(t)}}static{this.ɵfac=function(e){return new(e||Wa)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(K.TranslateService),t.ɵɵdirectiveInject(t.Injector),t.ɵɵdirectiveInject(k.FormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Wa,selectors:[["tb-kv-map-config-old"]],inputs:{disabled:"disabled",uniqueKeyValuePairValidator:"uniqueKeyValuePairValidator",requiredText:"requiredText",keyText:"keyText",keyRequiredText:"keyRequiredText",valText:"valText",valRequiredText:"valRequiredText",hintText:"hintText",required:"required"},features:[t.ɵɵProvidersFeature([{provide:V,useExisting:r((()=>Wa)),multi:!0},{provide:O,useExisting:r((()=>Wa)),multi:!0}]),t.ɵɵInheritDefinitionFeature],decls:21,vars:26,consts:[[1,"tb-kv-map-config","flex","flex-col",3,"formGroup"],[1,"header","flex","flex-1","flex-row","gap-2"],[1,"cell","tb-required","flex-1"],["innerHTML",t.ɵɵtrustConstantHtml` `,2,"width","52px"],[1,"body"],["class","row flex flex-row items-center justify-start gap-2","formArrayName","keyVals",4,"ngFor","ngForOf"],["class","tb-hint",3,"innerHTML",4,"ngIf"],[3,"error"],[2,"margin-top","16px"],["mat-button","","mat-raised-button","","color","primary","type","button","matTooltipPosition","above",3,"click","disabled","matTooltip"],["formArrayName","keyVals",1,"row","flex","flex-row","items-center","justify-start","gap-2"],[1,"cell","mat-block","flex-1"],["matInput","","required","",3,"formControl","placeholder"],[4,"ngIf"],["mat-icon-button","","color","primary","type","button","matTooltipPosition","above",3,"click","disabled","matTooltip"],[1,"tb-hint",3,"innerHTML"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"div",1)(2,"span",2),t.ɵɵtext(3),t.ɵɵpipe(4,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(5,"span",2),t.ɵɵtext(6),t.ɵɵpipe(7,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(8,"span",3),t.ɵɵelementEnd(),t.ɵɵelementStart(9,"div",4),t.ɵɵtemplate(10,Ja,14,18,"div",5)(11,Ya,3,6,"div",6),t.ɵɵelementEnd(),t.ɵɵelement(12,"tb-error",7),t.ɵɵelementStart(13,"div",8)(14,"button",9),t.ɵɵpipe(15,"translate"),t.ɵɵpipe(16,"async"),t.ɵɵlistener("click",(function(){return n.addKeyVal()})),t.ɵɵelementStart(17,"mat-icon"),t.ɵɵtext(18,"add"),t.ɵɵelementEnd(),t.ɵɵtext(19),t.ɵɵpipe(20,"translate"),t.ɵɵelementEnd()()()),2&e&&(t.ɵɵproperty("formGroup",n.kvListFormGroup),t.ɵɵadvance(3),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(4,13,n.keyText)),t.ɵɵadvance(3),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(7,15,n.valText)),t.ɵɵadvance(2),t.ɵɵclassProp("!hidden",n.disabled),t.ɵɵadvance(2),t.ɵɵproperty("ngForOf",n.keyValsFormArray().controls),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.hintText),t.ɵɵadvance(),t.ɵɵproperty("error",n.ngControl.hasError("kvMapRequired")||n.ngControl.hasError("uniqueKeyValuePair")?n.ngControl.hasError("kvMapRequired")?n.translate.instant(n.requiredText):n.translate.instant("tb.key-val.unique-key-value-pair-error",t.ɵɵpureFunction2(23,za,n.translate.instant(n.keyText),n.translate.instant(n.valText))):""),t.ɵɵadvance(2),t.ɵɵclassProp("!hidden",n.disabled),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(15,17,"tb.key-val.add-entry")),t.ɵɵproperty("disabled",t.ɵɵpipeBind1(16,19,n.isLoading$)),t.ɵɵadvance(5),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(20,21,"action.add")," "))},dependencies:t.ɵɵgetComponentDepsFactory(Wa),styles:["[_nghost-%COMP%] .tb-kv-map-config[_ngcontent-%COMP%]{margin-bottom:16px}[_nghost-%COMP%] .tb-kv-map-config[_ngcontent-%COMP%] .header[_ngcontent-%COMP%]{padding-left:5px;padding-right:5px;padding-bottom:5px}[_nghost-%COMP%] .tb-kv-map-config[_ngcontent-%COMP%] .header[_ngcontent-%COMP%] .cell[_ngcontent-%COMP%]{padding-left:5px;padding-right:5px;color:#757575;font-size:12px;font-weight:700;white-space:nowrap}[_nghost-%COMP%] .tb-kv-map-config[_ngcontent-%COMP%] .header[_ngcontent-%COMP%] .tb-required[_ngcontent-%COMP%]:after{color:#757575;font-size:12px;font-weight:700}[_nghost-%COMP%] .tb-kv-map-config[_ngcontent-%COMP%] .body[_ngcontent-%COMP%]{padding-left:5px;padding-right:5px;padding-bottom:0;max-height:300px;overflow:auto}[_nghost-%COMP%] .tb-kv-map-config[_ngcontent-%COMP%] .body[_ngcontent-%COMP%] .cell[_ngcontent-%COMP%]{padding-left:5px;padding-right:5px}[_nghost-%COMP%] .tb-kv-map-config[_ngcontent-%COMP%] tb-error[_ngcontent-%COMP%]{display:block;margin-top:-12px}"]})}}function Xa(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-chip-option",4),t.ɵɵtext(1),t.ɵɵelementEnd()),2&e){const e=n.$implicit,r=t.ɵɵnextContext();t.ɵɵpropertyInterpolate("selectable",r.chipControlGroup.get("chipControl").value!==e.value),t.ɵɵproperty("value",e.value),t.ɵɵadvance(),t.ɵɵtextInterpolate(e.name)}}e("KvMapConfigOldComponent",Wa);class Za{constructor(e,t){this.fb=e,this.translate=t,this.translation=Ht,this.propagateChange=()=>{},this.destroy$=new Y,this.selectOptions=[]}ngOnInit(){this.initOptions(),this.chipControlGroup=this.fb.group({chipControl:[null,[]]}),this.chipControlGroup.get("chipControl").valueChanges.pipe(ie(this.destroy$)).subscribe((e=>{e&&this.propagateChange(e)}))}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}initOptions(){for(const e of this.translation.keys())this.selectOptions.push({value:e,name:this.translate.instant(this.translation.get(e))})}writeValue(e){this.chipControlGroup.get("chipControl").patchValue(e,{emitEvent:!1})}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){e?this.chipControlGroup.disable({emitEvent:!1}):this.chipControlGroup.enable({emitEvent:!1})}static{this.ɵfac=function(e){return new(e||Za)(t.ɵɵdirectiveInject(k.FormBuilder),t.ɵɵdirectiveInject(K.TranslateService))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Za,selectors:[["tb-msg-metadata-chip"]],inputs:{labelText:"labelText",translation:"translation"},features:[t.ɵɵProvidersFeature([{provide:V,useExisting:r((()=>Za)),multi:!0}])],decls:5,vars:3,consts:[[1,"tb-form-row","space-between",3,"formGroup"],[1,"fixed-title-width"],["formControlName","chipControl"],["color","primary",3,"selectable","value",4,"ngFor","ngForOf"],["color","primary",3,"selectable","value"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"div",0)(1,"div",1),t.ɵɵtext(2),t.ɵɵelementEnd(),t.ɵɵelementStart(3,"mat-chip-listbox",2),t.ɵɵtemplate(4,Xa,2,3,"mat-chip-option",3),t.ɵɵelementEnd()()),2&e&&(t.ɵɵproperty("formGroup",n.chipControlGroup),t.ɵɵadvance(2),t.ɵɵtextInterpolate(n.labelText),t.ɵɵadvance(2),t.ɵɵproperty("ngForOf",n.selectOptions))},dependencies:t.ɵɵgetComponentDepsFactory(Za),encapsulation:2})}}function ei(e,n){1&e&&(t.ɵɵelementStart(0,"div",13),t.ɵɵtext(1," tb.rulenode.map-fields-required "),t.ɵɵelementEnd())}function ti(e,n){if(1&e&&(t.ɵɵelementStart(0,"div",13),t.ɵɵtext(1),t.ɵɵelementEnd()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",e.requiredText," ")}}function ni(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",21),t.ɵɵtext(1),t.ɵɵelementEnd()),2&e){const e=n.$implicit;t.ɵɵproperty("value",e.value),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",e.name," ")}}function ri(e,n){if(1&e){const e=t.ɵɵgetCurrentView();t.ɵɵelementStart(0,"div",14)(1,"mat-form-field",15)(2,"mat-select",16),t.ɵɵtemplate(3,ni,2,2,"mat-option",17),t.ɵɵelementEnd()(),t.ɵɵelementStart(4,"mat-form-field",15),t.ɵɵelement(5,"input",18),t.ɵɵelementEnd(),t.ɵɵelementStart(6,"div",19)(7,"button",20),t.ɵɵpipe(8,"translate"),t.ɵɵpipe(9,"async"),t.ɵɵlistener("click",(function(){const n=t.ɵɵrestoreView(e).index,r=t.ɵɵnextContext();return t.ɵɵresetView(r.removeKeyVal(n))})),t.ɵɵelementStart(10,"mat-icon"),t.ɵɵtext(11,"delete"),t.ɵɵelementEnd()()()()}if(2&e){const e=n.$implicit,r=t.ɵɵnextContext();t.ɵɵadvance(2),t.ɵɵproperty("placeholder",r.selectText)("formControl",e.get("key")),t.ɵɵadvance(),t.ɵɵproperty("ngForOf",r.filterSelectOptions(e)),t.ɵɵadvance(2),t.ɵɵproperty("placeholder",r.valText)("formControl",e.get("value")),t.ɵɵadvance(2),t.ɵɵclassProp("tb-hidden",1===r.keyValsFormArray().controls.length),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(8,9,"tb.key-val.remove-mapping-entry")),t.ɵɵproperty("disabled",t.ɵɵpipeBind1(9,11,r.isLoading$))}}e("MsgMetadataChipComponent",Za);class ai extends y{constructor(e,t,n,r){super(e),this.store=e,this.translate=t,this.injector=n,this.fb=r,this.destroy$=new Y,this.sourceFieldSubcritption=[],this.propagateChange=null,this.disabled=!1,this.required=!1,this.oneMapRequiredValidator=e=>e.get("keyVals").value.length,this.propagateNestedErrors=e=>{if(this.svListFormGroup&&this.svListFormGroup.get("keyVals")&&"VALID"===this.svListFormGroup.get("keyVals")?.status)return null;const t={};if(this.svListFormGroup&&this.svListFormGroup.setErrors(null),e instanceof w||e instanceof M){if(e.errors)for(const n of Object.keys(e.errors))t[n]=!0;for(const n of Object.keys(e.controls)){const r=this.propagateNestedErrors(e.controls[n]);if(r&&Object.keys(r).length)for(const e of Object.keys(r))t[e]=!0}return t}if(e.errors)for(const n of Object.keys(e.errors))t[n]=!0;return R(t,{})?null:t}}ngOnInit(){this.ngControl=this.injector.get(B),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.svListFormGroup=this.fb.group({keyVals:this.fb.array([])},{validators:[this.propagateNestedErrors,this.oneMapRequiredValidator]}),this.svListFormGroup.valueChanges.pipe(ie(this.destroy$)).subscribe((()=>{this.updateModel()}))}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}keyValsFormArray(){return this.svListFormGroup.get("keyVals")}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.svListFormGroup.disable({emitEvent:!1}):this.svListFormGroup.enable({emitEvent:!1})}writeValue(e){const t=Object.keys(e).map((t=>({key:t,value:e[t]})));if(this.keyValsFormArray().length===t.length)this.keyValsFormArray().patchValue(t,{emitEvent:!1});else{const e=[];t.forEach((t=>{e.push(this.fb.group({key:[t.key,[N.required]],value:[t.value,[N.required,N.pattern(/(?:.|\s)*\S(&:.|\s)*/)]]}))})),this.svListFormGroup.setControl("keyVals",this.fb.array(e,this.propagateNestedErrors),{emitEvent:!1});for(const e of this.keyValsFormArray().controls)this.keyChangeSubscribe(e)}}filterSelectOptions(e){const t=[];for(const e of this.svListFormGroup.get("keyVals").value){const n=this.selectOptions.find((t=>t.value===e.key));n&&t.push(n)}const n=[];for(const r of this.selectOptions)P(t.find((e=>e.value===r.value)))&&r.value!==e?.get("key").value||n.push(r);return n}removeKeyVal(e){this.keyValsFormArray().removeAt(e),this.sourceFieldSubcritption[e].unsubscribe(),this.sourceFieldSubcritption.splice(e,1)}addKeyVal(){this.keyValsFormArray().push(this.fb.group({key:["",[N.required]],value:["",[N.required,N.pattern(/(?:.|\s)*\S(&:.|\s)*/)]]})),this.keyChangeSubscribe(this.keyValsFormArray().at(this.keyValsFormArray().length-1))}keyChangeSubscribe(e){this.sourceFieldSubcritption.push(e.get("key").valueChanges.pipe(ie(this.destroy$)).subscribe((t=>{const n=ct.get(t);e.get("value").patchValue(this.targetKeyPrefix+n[0].toUpperCase()+n.slice(1))})))}validate(e){return!this.svListFormGroup.get("keyVals").value.length&&this.required?{svMapRequired:!0}:this.svListFormGroup.valid?null:{svFieldsRequired:!0}}updateModel(){const e=this.svListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.svListFormGroup.valid)this.propagateChange(null);else{const t={};e.forEach((e=>{t[e.key]=e.value})),this.propagateChange(t)}}static{this.ɵfac=function(e){return new(e||ai)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(K.TranslateService),t.ɵɵdirectiveInject(t.Injector),t.ɵɵdirectiveInject(k.FormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:ai,selectors:[["tb-sv-map-config"]],inputs:{selectOptions:"selectOptions",disabled:"disabled",labelText:"labelText",requiredText:"requiredText",targetKeyPrefix:"targetKeyPrefix",selectText:"selectText",selectRequiredText:"selectRequiredText",valText:"valText",valRequiredText:"valRequiredText",hintText:"hintText",popupHelpLink:"popupHelpLink",required:"required"},features:[t.ɵɵProvidersFeature([{provide:V,useExisting:r((()=>ai)),multi:!0},{provide:O,useExisting:r((()=>ai)),multi:!0}]),t.ɵɵInheritDefinitionFeature],decls:22,vars:15,consts:[[1,"tb-form-panel","stroked",3,"formGroup"],[1,"tb-form-row","no-padding","no-border","space-between"],[1,"tb-form-panel-title"],["class","tb-form-panel-hint tb-error","translate","",4,"ngIf"],[1,"tb-form-panel","no-border","no-padding"],[1,"tb-form-table"],[1,"tb-form-table-header"],[1,"tb-form-table-header-cell","field-space"],[1,"tb-form-table-header-cell","actions-header"],[1,"tb-form-table-body"],["class","tb-form-table-row",4,"ngFor","ngForOf"],["type","button","mat-stroked-button","","color","primary",3,"click","disabled"],[3,"hintText","popupHelpLink"],["translate","",1,"tb-form-panel-hint","tb-error"],[1,"tb-form-table-row"],["appearance","outline","subscriptSizing","dynamic",1,"tb-inline-field","field-space"],["required","",3,"placeholder","formControl"],[3,"value",4,"ngFor","ngForOf"],["matInput","",3,"placeholder","formControl"],[1,"tb-form-table-row-cell-buttons"],["type","button","mat-icon-button","","matTooltipPosition","above",3,"click","disabled","matTooltip"],[3,"value"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"div",1)(2,"div",2),t.ɵɵtext(3),t.ɵɵelementEnd(),t.ɵɵtemplate(4,ei,2,0,"div",3)(5,ti,2,1,"div",3),t.ɵɵelementEnd(),t.ɵɵelementStart(6,"div",4)(7,"div",5)(8,"div",6)(9,"div",7),t.ɵɵtext(10),t.ɵɵelementEnd(),t.ɵɵelementStart(11,"div",7),t.ɵɵtext(12),t.ɵɵelementEnd(),t.ɵɵelement(13,"div",8),t.ɵɵelementEnd(),t.ɵɵelementStart(14,"div",9),t.ɵɵtemplate(15,ri,12,13,"div",10),t.ɵɵelementEnd()()(),t.ɵɵelementStart(16,"div")(17,"button",11),t.ɵɵpipe(18,"async"),t.ɵɵlistener("click",(function(){return n.addKeyVal()})),t.ɵɵtext(19),t.ɵɵpipe(20,"translate"),t.ɵɵelementEnd()(),t.ɵɵelement(21,"tb-example-hint",12),t.ɵɵelementEnd()),2&e&&(t.ɵɵproperty("formGroup",n.svListFormGroup),t.ɵɵadvance(3),t.ɵɵtextInterpolate(n.labelText),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.svListFormGroup.hasError("required")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.svListFormGroup.hasError("svMapRequired")),t.ɵɵadvance(5),t.ɵɵtextInterpolate(n.selectText),t.ɵɵadvance(2),t.ɵɵtextInterpolate(n.valText),t.ɵɵadvance(3),t.ɵɵproperty("ngForOf",n.keyValsFormArray().controls),t.ɵɵadvance(2),t.ɵɵproperty("disabled",t.ɵɵpipeBind1(18,11,n.isLoading$)||n.keyValsFormArray().length>=n.selectOptions.length),t.ɵɵadvance(2),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(20,13,"tb.key-val.add-mapping-entry")," "),t.ɵɵadvance(2),t.ɵɵproperty("hintText",n.hintText)("popupHelpLink",n.popupHelpLink))},dependencies:t.ɵɵgetComponentDepsFactory(ai),styles:["[_nghost-%COMP%] .field-space[_ngcontent-%COMP%]{flex:1 1 50%}[_nghost-%COMP%] .actions-header[_ngcontent-%COMP%]{width:40px}"]})}}function ii(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",11),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=n.$implicit,r=t.ɵɵnextContext();t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,2,r.directionTypeTranslations.get(e))," ")}}e("SvMapConfigComponent",ai),J([h()],ai.prototype,"disabled",void 0),J([h()],ai.prototype,"required",void 0);class oi extends y{get required(){return this.requiredValue}set required(e){this.requiredValue=Z(e)}constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(d),this.directionTypeTranslations=b,this.propagateChange=null}ngOnInit(){this.relationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[N.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((e=>{this.relationsQueryFormGroup.valid?this.propagateChange(e):this.propagateChange(null)}))}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})}writeValue(e){this.relationsQueryFormGroup.reset(e||{},{emitEvent:!1})}static{this.ɵfac=function(e){return new(e||oi)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.FormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:oi,selectors:[["tb-relations-query-config-old"]],inputs:{disabled:"disabled",required:"required"},features:[t.ɵɵProvidersFeature([{provide:V,useExisting:r((()=>oi)),multi:!0}]),t.ɵɵInheritDefinitionFeature],decls:18,vars:8,consts:[[1,"flex","flex-col",3,"formGroup"],["formControlName","fetchLastLevelOnly"],[1,"flex","flex-row","gap-2"],[1,"mat-block",2,"min-width","100px"],["translate",""],["required","","matInput","","formControlName","direction"],[3,"value",4,"ngFor","ngForOf"],["floatLabel","always",1,"mat-block","flex-1"],["matInput","","type","number","min","1","step","1","formControlName","maxLevel",3,"placeholder"],["translate","",1,"mat-caption",2,"color","#6e6e6e"],["formControlName","filters"],[3,"value"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"mat-checkbox",1),t.ɵɵtext(2),t.ɵɵpipe(3,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(4,"div",2)(5,"mat-form-field",3)(6,"mat-label",4),t.ɵɵtext(7,"relation.direction"),t.ɵɵelementEnd(),t.ɵɵelementStart(8,"mat-select",5),t.ɵɵtemplate(9,ii,3,4,"mat-option",6),t.ɵɵelementEnd()(),t.ɵɵelementStart(10,"mat-form-field",7)(11,"mat-label",4),t.ɵɵtext(12,"tb.rulenode.max-relation-level"),t.ɵɵelementEnd(),t.ɵɵelement(13,"input",8),t.ɵɵpipe(14,"translate"),t.ɵɵelementEnd()(),t.ɵɵelementStart(15,"div",9),t.ɵɵtext(16,"relation.relation-filters"),t.ɵɵelementEnd(),t.ɵɵelement(17,"tb-relation-filters",10),t.ɵɵelementEnd()),2&e&&(t.ɵɵproperty("formGroup",n.relationsQueryFormGroup),t.ɵɵadvance(2),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(3,4,"alias.last-level-relation")," "),t.ɵɵadvance(7),t.ɵɵproperty("ngForOf",n.directionTypes),t.ɵɵadvance(4),t.ɵɵpropertyInterpolate("placeholder",t.ɵɵpipeBind1(14,6,"tb.rulenode.unlimited-level")))},dependencies:t.ɵɵgetComponentDepsFactory(oi),encapsulation:2})}}e("RelationsQueryConfigOldComponent",oi);const li=e=>({latestTsKeyName:e}),si=e=>({inputName:e});function pi(e,n){1&e&&t.ɵɵelementContainer(0,9)}function mi(e,n){1&e&&t.ɵɵelementContainer(0,9)}function di(e,n){1&e&&t.ɵɵelementContainer(0,9)}function ui(e,n){1&e&&t.ɵɵelementContainer(0,9)}function ci(e,n){if(1&e&&(t.ɵɵelementStart(0,"div",10),t.ɵɵpipe(1,"translate"),t.ɵɵelementStart(2,"mat-slide-toggle",11),t.ɵɵtext(3),t.ɵɵpipe(4,"translate"),t.ɵɵelementEnd()()),2&e){const e=t.ɵɵnextContext();t.ɵɵpropertyInterpolate("tb-hint-tooltip-icon",t.ɵɵpipeBind2(1,2,"tb.rulenode.fetch-latest-telemetry-with-timestamp-tooltip",t.ɵɵpureFunction1(7,li,e.attributeControlGroup.get("latestTsKeyNames").value[0]))),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(4,5,"tb.rulenode.fetch-latest-telemetry-with-timestamp")," ")}}function fi(e,n){1&e&&(t.ɵɵelementStart(0,"mat-icon",12),t.ɵɵpipe(1,"translate"),t.ɵɵpipe(2,"translate"),t.ɵɵtext(3,"help"),t.ɵɵelementEnd()),2&e&&t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind2(2,3,"tb.rulenode.chip-help",t.ɵɵpureFunction1(6,si,t.ɵɵpipeBind1(1,1,"tb.rulenode.field-name"))))}class gi{constructor(e,t){this.translate=e,this.fb=t,this.propagateChange=e=>{},this.destroy$=new Y,this.separatorKeysCodes=[U,H,z],this.onTouched=()=>{}}ngOnInit(){this.attributeControlGroup=this.fb.group({clientAttributeNames:[[],[]],sharedAttributeNames:[[],[]],serverAttributeNames:[[],[]],latestTsKeyNames:[[],[]],getLatestValueWithTs:[!1,[]]},{validators:this.atLeastOne(N.required,["clientAttributeNames","sharedAttributeNames","serverAttributeNames","latestTsKeyNames"])}),this.attributeControlGroup.valueChanges.pipe(ie(this.destroy$)).subscribe((e=>{this.propagateChange(this.preparePropagateValue(e))}))}preparePropagateValue(e){const t={};for(const n in e)t[n]="getLatestValueWithTs"===n||P(e[n])?e[n]:[];return t}validate(){return this.attributeControlGroup.valid?null:{atLeastOneRequired:!0}}atLeastOne(e,t=null){return n=>{t||(t=Object.keys(n.controls));return n?.controls&&t.some((t=>!e(n.controls[t])))?null:{atLeastOne:!0}}}writeValue(e){this.attributeControlGroup.setValue(e,{emitEvent:!1})}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){this.onTouched=e}setDisabledState(e){e?this.attributeControlGroup.disable({emitEvent:!1}):this.attributeControlGroup.enable({emitEvent:!1})}ngOnDestroy(){this.destroy$.next(null),this.destroy$.complete()}static{this.ɵfac=function(e){return new(e||gi)(t.ɵɵdirectiveInject(K.TranslateService),t.ɵɵdirectiveInject(k.FormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:gi,selectors:[["tb-select-attributes"]],inputs:{popupHelpLink:"popupHelpLink"},features:[t.ɵɵProvidersFeature([{provide:V,useExisting:r((()=>gi)),multi:!0},{provide:O,useExisting:gi,multi:!0}])],decls:22,vars:34,consts:[["helpIcon",""],[1,"tb-form-panel","no-padding","no-border",3,"formGroup"],[3,"hintText","popupHelpLink"],["subscriptSizing","dynamic","editable","","formControlName","clientAttributeNames",1,"mat-block",3,"focusout","placeholder","label"],["matSuffix","",4,"ngTemplateOutlet"],["subscriptSizing","dynamic","editable","","formControlName","sharedAttributeNames",1,"mat-block",3,"focusout","placeholder","label"],["subscriptSizing","dynamic","editable","","formControlName","serverAttributeNames",1,"mat-block",3,"focusout","placeholder","label"],["subscriptSizing","dynamic","editable","","formControlName","latestTsKeyNames",1,"mat-block",3,"focusout","placeholder","label"],["class","tb-form-row no-border no-padding",3,"tb-hint-tooltip-icon",4,"ngIf"],["matSuffix",""],[1,"tb-form-row","no-border","no-padding",3,"tb-hint-tooltip-icon"],["formControlName","getLatestValueWithTs",1,"mat-slide"],["aria-hidden","false","aria-label","help-icon","color","primary",1,"help-icon","margin-8","cursor-pointer",3,"matTooltip"]],template:function(e,n){if(1&e){const e=t.ɵɵgetCurrentView();t.ɵɵelementStart(0,"div",1),t.ɵɵelement(1,"tb-example-hint",2),t.ɵɵpipe(2,"translate"),t.ɵɵelementStart(3,"tb-string-items-list",3),t.ɵɵpipe(4,"translate"),t.ɵɵpipe(5,"translate"),t.ɵɵlistener("focusout",(function(){return t.ɵɵrestoreView(e),t.ɵɵresetView(n.onTouched())})),t.ɵɵtemplate(6,pi,1,0,"ng-container",4),t.ɵɵelementEnd(),t.ɵɵelementStart(7,"tb-string-items-list",5),t.ɵɵpipe(8,"translate"),t.ɵɵpipe(9,"translate"),t.ɵɵlistener("focusout",(function(){return t.ɵɵrestoreView(e),t.ɵɵresetView(n.onTouched())})),t.ɵɵtemplate(10,mi,1,0,"ng-container",4),t.ɵɵelementEnd(),t.ɵɵelementStart(11,"tb-string-items-list",6),t.ɵɵpipe(12,"translate"),t.ɵɵpipe(13,"translate"),t.ɵɵlistener("focusout",(function(){return t.ɵɵrestoreView(e),t.ɵɵresetView(n.onTouched())})),t.ɵɵtemplate(14,di,1,0,"ng-container",4),t.ɵɵelementEnd(),t.ɵɵelementStart(15,"tb-string-items-list",7),t.ɵɵpipe(16,"translate"),t.ɵɵpipe(17,"translate"),t.ɵɵlistener("focusout",(function(){return t.ɵɵrestoreView(e),t.ɵɵresetView(n.onTouched())})),t.ɵɵtemplate(18,ui,1,0,"ng-container",4),t.ɵɵelementEnd(),t.ɵɵtemplate(19,ci,5,9,"div",8),t.ɵɵelementEnd(),t.ɵɵtemplate(20,fi,4,8,"ng-template",null,0,t.ɵɵtemplateRefExtractor)}if(2&e){let e;const r=t.ɵɵreference(21);t.ɵɵproperty("formGroup",n.attributeControlGroup),t.ɵɵadvance(),t.ɵɵproperty("hintText",t.ɵɵpipeBind1(2,16,"tb.rulenode.kv-map-pattern-hint"))("popupHelpLink",n.popupHelpLink),t.ɵɵadvance(2),t.ɵɵproperty("placeholder",t.ɵɵpipeBind1(4,18,"tb.rulenode.add-attribute-key"))("label",t.ɵɵpipeBind1(5,20,"tb.rulenode.client-attributes")),t.ɵɵadvance(3),t.ɵɵproperty("ngTemplateOutlet",r),t.ɵɵadvance(),t.ɵɵproperty("placeholder",t.ɵɵpipeBind1(8,22,"tb.rulenode.add-attribute-key"))("label",t.ɵɵpipeBind1(9,24,"tb.rulenode.shared-attributes")),t.ɵɵadvance(3),t.ɵɵproperty("ngTemplateOutlet",r),t.ɵɵadvance(),t.ɵɵproperty("placeholder",t.ɵɵpipeBind1(12,26,"tb.rulenode.add-attribute-key"))("label",t.ɵɵpipeBind1(13,28,"tb.rulenode.server-attributes")),t.ɵɵadvance(3),t.ɵɵproperty("ngTemplateOutlet",r),t.ɵɵadvance(),t.ɵɵproperty("placeholder",t.ɵɵpipeBind1(16,30,"tb.rulenode.add-telemetry-key"))("label",t.ɵɵpipeBind1(17,32,"tb.rulenode.latest-telemetry")),t.ɵɵadvance(3),t.ɵɵproperty("ngTemplateOutlet",r),t.ɵɵadvance(),t.ɵɵproperty("ngIf",(null==(e=n.attributeControlGroup.get("latestTsKeyNames").value)?null:e.length)>0)}},dependencies:t.ɵɵgetComponentDepsFactory(gi),encapsulation:2})}}e("SelectAttributesComponent",gi);class hi extends y{constructor(e,t){super(e),this.store=e,this.fb=t,this.propagateChange=null,this.destroy$=new Y,this.alarmStatus=x,this.alarmStatusTranslations=C}ngOnInit(){this.alarmStatusGroup=this.fb.group({alarmStatus:[null,[]]}),this.alarmStatusGroup.get("alarmStatus").valueChanges.pipe(ie(this.destroy$)).subscribe((e=>{this.propagateChange(e)}))}setDisabledState(e){e?this.alarmStatusGroup.disable({emitEvent:!1}):this.alarmStatusGroup.enable({emitEvent:!1})}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}writeValue(e){this.alarmStatusGroup.get("alarmStatus").patchValue(e,{emitEvent:!1})}static{this.ɵfac=function(e){return new(e||hi)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.FormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:hi,selectors:[["tb-alarm-status-select"]],features:[t.ɵɵProvidersFeature([{provide:V,useExisting:r((()=>hi)),multi:!0}]),t.ɵɵInheritDefinitionFeature],decls:16,vars:17,consts:[[1,"flex","flex-col","items-center","justify-center",3,"formGroup"],["multiple","","formControlName","alarmStatus",1,"chip-listbox","flex","flex-col"],[1,"toggle-column"],[1,"option","flex-1",3,"value"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"mat-chip-listbox",1)(2,"div",2)(3,"mat-chip-option",3),t.ɵɵtext(4),t.ɵɵpipe(5,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(6,"mat-chip-option",3),t.ɵɵtext(7),t.ɵɵpipe(8,"translate"),t.ɵɵelementEnd()(),t.ɵɵelementStart(9,"div",2)(10,"mat-chip-option",3),t.ɵɵtext(11),t.ɵɵpipe(12,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(13,"mat-chip-option",3),t.ɵɵtext(14),t.ɵɵpipe(15,"translate"),t.ɵɵelementEnd()()()()),2&e&&(t.ɵɵproperty("formGroup",n.alarmStatusGroup),t.ɵɵadvance(3),t.ɵɵproperty("value",n.alarmStatus.ACTIVE_UNACK),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(5,9,n.alarmStatusTranslations.get(n.alarmStatus.ACTIVE_UNACK))," "),t.ɵɵadvance(2),t.ɵɵproperty("value",n.alarmStatus.ACTIVE_ACK),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(8,11,n.alarmStatusTranslations.get(n.alarmStatus.ACTIVE_ACK))," "),t.ɵɵadvance(3),t.ɵɵproperty("value",n.alarmStatus.CLEARED_UNACK),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(12,13,n.alarmStatusTranslations.get(n.alarmStatus.CLEARED_UNACK))," "),t.ɵɵadvance(2),t.ɵɵproperty("value",n.alarmStatus.CLEARED_ACK),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(15,15,n.alarmStatusTranslations.get(n.alarmStatus.CLEARED_ACK))," "))},dependencies:t.ɵɵgetComponentDepsFactory(hi),styles:["[_nghost-%COMP%] .chip-listbox[_ngcontent-%COMP%]{max-width:460px;width:100%}[_nghost-%COMP%] .chip-listbox[_ngcontent-%COMP%] .toggle-column[_ngcontent-%COMP%]{display:flex;flex:1 1 100%;gap:8px}[_nghost-%COMP%] .chip-listbox[_ngcontent-%COMP%] .option[_ngcontent-%COMP%]{margin:0}@media screen and (max-width: 959px){[_nghost-%COMP%] .chip-listbox[_ngcontent-%COMP%]{max-width:360px}[_nghost-%COMP%] .chip-listbox[_ngcontent-%COMP%] .toggle-column[_ngcontent-%COMP%]{flex-direction:column}}[_nghost-%COMP%] .chip-listbox .mdc-evolution-chip-set__chips{gap:8px}[_nghost-%COMP%] .chip-listbox .option button{flex-basis:100%;justify-content:start}[_nghost-%COMP%] .chip-listbox .option .mdc-evolution-chip__graphic{flex-grow:0}"]})}}e("AlarmStatusSelectComponent",hi);const yi=()=>({maxWidth:"820px"});function bi(e,n){if(1&e&&(t.ɵɵelement(0,"div",3),t.ɵɵpipe(1,"translate")),2&e){const e=t.ɵɵnextContext();t.ɵɵpropertyInterpolate("tb-help-popup",e.popupHelpLink),t.ɵɵpropertyInterpolate("trigger-text",t.ɵɵpipeBind1(1,3,"tb.key-val.see-examples")),t.ɵɵproperty("tb-help-popup-style",t.ɵɵpureFunction0(5,yi))}}class vi{constructor(){this.textAlign="left"}static{this.ɵfac=function(e){return new(e||vi)}}static{this.ɵcmp=t.ɵɵdefineComponent({type:vi,selectors:[["tb-example-hint"]],inputs:{hintText:"hintText",popupHelpLink:"popupHelpLink",textAlign:"textAlign"},decls:5,vars:10,consts:[[1,"tb-form-hint","tb-primary-fill","space-between",3,"hidden"],[1,"hint-text",3,"innerHTML"],["class","see-example","hintMode","","tb-help-popup-placement","right","trigger-style","letter-spacing:0.25px; font-size:12px",3,"tb-help-popup","tb-help-popup-style","trigger-text",4,"ngIf"],["hintMode","","tb-help-popup-placement","right","trigger-style","letter-spacing:0.25px; font-size:12px",1,"see-example",3,"tb-help-popup","tb-help-popup-style","trigger-text"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"div",0),t.ɵɵelement(1,"div",1),t.ɵɵpipe(2,"translate"),t.ɵɵpipe(3,"safe"),t.ɵɵtemplate(4,bi,2,6,"div",2),t.ɵɵelementEnd()),2&e&&(t.ɵɵproperty("hidden",!n.hintText),t.ɵɵadvance(),t.ɵɵstyleProp("text-align",n.textAlign),t.ɵɵproperty("innerHTML",t.ɵɵpipeBind2(3,7,t.ɵɵpipeBind1(2,5,n.hintText),"html"),t.ɵɵsanitizeHtml),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",n.popupHelpLink))},dependencies:t.ɵɵgetComponentDepsFactory(vi),styles:["[_nghost-%COMP%] .space-between[_ngcontent-%COMP%]{display:flex;justify-content:space-between;gap:20px}[_nghost-%COMP%] .space-between[_ngcontent-%COMP%] .see-example[_ngcontent-%COMP%]{display:flex;flex-shrink:0}[_nghost-%COMP%] .hint-text[_ngcontent-%COMP%]{width:100%}"]})}}e("ExampleHintComponent",vi);class xi{static{this.ɵfac=function(e){return new(e||xi)}}static{this.ɵmod=t.ɵɵdefineNgModule({type:xi})}static{this.ɵinj=t.ɵɵdefineInjector({imports:[$,S,Q,_r,zr,Wr,ma,va,Oa,_a,Ha,Wa,Za,ai,oi,gi,hi,vi]})}}e("RulenodeCoreConfigCommonModule",xi),("undefined"==typeof ngJitMode||ngJitMode)&&t.ɵɵsetNgModuleScope(xi,{declarations:[_r,zr,Wr,ma,va,Oa,_a,Ha,Wa,Za,ai,oi,gi,hi,vi],imports:[$,S,Q],exports:[_r,zr,Wr,ma,va,Oa,_a,Ha,Wa,Za,ai,oi,gi,hi,vi]});class Ci{static{this.ɵfac=function(e){return new(e||Ci)}}static{this.ɵmod=t.ɵɵdefineNgModule({type:Ci})}static{this.ɵinj=t.ɵɵdefineInjector({imports:[$,S,Q,xi,Cr,ce,mr,rr,Vn,se,Ce,Re,He,$n,Ye,st,qn,Pn,er,lr,cr,fr,We,Zn,Yn,wr,Br]})}}e("RuleNodeCoreConfigActionModule",Ci),("undefined"==typeof ngJitMode||ngJitMode)&&t.ɵɵsetNgModuleScope(Ci,{declarations:[Cr,ce,mr,rr,Vn,se,Ce,Re,He,$n,Ye,st,qn,Pn,er,lr,cr,fr,We,Zn,Yn,wr,Br],imports:[$,S,Q,xi],exports:[Cr,ce,mr,rr,Vn,se,Ce,Re,He,$n,Ye,st,qn,Pn,er,lr,cr,fr,We,Zn,Yn,wr,Br]});const Si=e=>({inputValueKey:e}),Ti=e=>({periodValueKey:e}),Ii=(e,t)=>({outputValueKey:e,periodValueKey:t}),Ei=e=>({outputValueKey:e});function Fi(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.input-value-key-required")," "))}function qi(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.output-value-key-required")," "))}function Ai(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.number-of-digits-after-floating-point-range")," "))}function ki(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.number-of-digits-after-floating-point-range")," "))}function Ni(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.period-value-key-required")," "))}function wi(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-form-field",16)(1,"mat-label"),t.ɵɵtext(2),t.ɵɵpipe(3,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(4,"input",17),t.ɵɵtemplate(5,Ni,3,3,"mat-error",4),t.ɵɵelementEnd()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(2),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(3,2,"tb.rulenode.period-value-key")),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",e.calculateDeltaConfigForm.get("periodValueKey").hasError("required"))}}class Mi extends i{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n,this.separatorKeysCodes=[U,H,z]}configForm(){return this.calculateDeltaConfigForm}onConfigurationSet(e){this.calculateDeltaConfigForm=this.fb.group({inputValueKey:[e.inputValueKey,[N.required,N.pattern(/(?:.|\s)*\S(&:.|\s)*/)]],outputValueKey:[e.outputValueKey,[N.required,N.pattern(/(?:.|\s)*\S(&:.|\s)*/)]],useCache:[e.useCache,[]],addPeriodBetweenMsgs:[e.addPeriodBetweenMsgs,[]],periodValueKey:[e.periodValueKey,[]],round:[e.round,[N.min(0),N.max(15)]],tellFailureIfDeltaIsNegative:[e.tellFailureIfDeltaIsNegative,[]],excludeZeroDeltas:[e.excludeZeroDeltas,[]]})}prepareInputConfig(e){return{inputValueKey:P(e?.inputValueKey)?e.inputValueKey:null,outputValueKey:P(e?.outputValueKey)?e.outputValueKey:null,useCache:!P(e?.useCache)||e.useCache,addPeriodBetweenMsgs:!!P(e?.addPeriodBetweenMsgs)&&e.addPeriodBetweenMsgs,periodValueKey:P(e?.periodValueKey)?e.periodValueKey:null,round:P(e?.round)?e.round:null,tellFailureIfDeltaIsNegative:!P(e?.tellFailureIfDeltaIsNegative)||e.tellFailureIfDeltaIsNegative,excludeZeroDeltas:!!P(e?.excludeZeroDeltas)&&e.excludeZeroDeltas}}prepareOutputConfig(e){return _(e)}updateValidators(e){this.calculateDeltaConfigForm.get("addPeriodBetweenMsgs").value?this.calculateDeltaConfigForm.get("periodValueKey").setValidators([N.required]):this.calculateDeltaConfigForm.get("periodValueKey").setValidators([]),this.calculateDeltaConfigForm.get("periodValueKey").updateValueAndValidity({emitEvent:e})}validatorTriggers(){return["addPeriodBetweenMsgs"]}static{this.ɵfac=function(e){return new(e||Mi)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(K.TranslateService),t.ɵɵdirectiveInject(k.FormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Mi,selectors:[["tb-enrichment-node-calculate-delta-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:50,vars:69,consts:[[3,"formGroup"],[1,"gt-sm:flex","gt-sm:flex-row","gt-sm:gap-5.5"],[1,"mat-block","flex-1"],["matInput","","formControlName","inputValueKey"],[4,"ngIf"],["matInput","","formControlName","outputValueKey"],["type","number","min","0","max","15","step","1","matInput","","formControlName","round"],[1,"tb-form-panel","no-padding","no-border"],[1,"tb-form-row","same-padding",3,"tb-hint-tooltip-icon"],["formControlName","tellFailureIfDeltaIsNegative",1,"mat-slide","margin"],["formControlName","useCache",1,"mat-slide","margin"],[1,"tb-form-panel","stroked"],[1,"tb-form-row","no-border","no-padding",3,"tb-hint-tooltip-icon"],["formControlName","addPeriodBetweenMsgs",1,"mat-slide"],["class","mat-block",4,"ngIf"],["formControlName","excludeZeroDeltas",1,"mat-slide","margin"],[1,"mat-block"],["required","","matInput","","formControlName","periodValueKey"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"div",1)(2,"mat-form-field",2)(3,"mat-label"),t.ɵɵtext(4),t.ɵɵpipe(5,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(6,"input",3),t.ɵɵtemplate(7,Fi,3,3,"mat-error",4),t.ɵɵelementEnd(),t.ɵɵelementStart(8,"mat-form-field",2)(9,"mat-label"),t.ɵɵtext(10),t.ɵɵpipe(11,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(12,"input",5),t.ɵɵtemplate(13,qi,3,3,"mat-error",4),t.ɵɵelementEnd()(),t.ɵɵelementStart(14,"mat-form-field",2)(15,"mat-label"),t.ɵɵtext(16),t.ɵɵpipe(17,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(18,"input",6),t.ɵɵtemplate(19,Ai,3,3,"mat-error",4)(20,ki,3,3,"mat-error",4),t.ɵɵelementEnd(),t.ɵɵelementStart(21,"div",7)(22,"div",8),t.ɵɵpipe(23,"translate"),t.ɵɵelementStart(24,"mat-slide-toggle",9),t.ɵɵtext(25),t.ɵɵpipe(26,"translate"),t.ɵɵelementEnd()(),t.ɵɵelementStart(27,"div",8),t.ɵɵpipe(28,"translate"),t.ɵɵpipe(29,"translate"),t.ɵɵelementStart(30,"mat-slide-toggle",10),t.ɵɵtext(31),t.ɵɵpipe(32,"translate"),t.ɵɵelementEnd()(),t.ɵɵelementStart(33,"div",11)(34,"div",12),t.ɵɵpipe(35,"translate"),t.ɵɵelementStart(36,"mat-slide-toggle",13),t.ɵɵtext(37),t.ɵɵpipe(38,"translate"),t.ɵɵpipe(39,"translate"),t.ɵɵelementEnd()(),t.ɵɵtemplate(40,wi,6,4,"mat-form-field",14),t.ɵɵelementEnd(),t.ɵɵelementStart(41,"div",8),t.ɵɵpipe(42,"translate"),t.ɵɵpipe(43,"translate"),t.ɵɵpipe(44,"translate"),t.ɵɵpipe(45,"translate"),t.ɵɵpipe(46,"translate"),t.ɵɵelementStart(47,"mat-slide-toggle",15),t.ɵɵtext(48),t.ɵɵpipe(49,"translate"),t.ɵɵelementEnd()()()()),2&e&&(t.ɵɵproperty("formGroup",n.calculateDeltaConfigForm),t.ɵɵadvance(4),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(5,19,"tb.rulenode.input-value-key")),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",n.calculateDeltaConfigForm.get("inputValueKey").hasError("required")||n.calculateDeltaConfigForm.get("inputValueKey").hasError("pattern")),t.ɵɵadvance(3),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(11,21,"tb.rulenode.output-value-key")),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",n.calculateDeltaConfigForm.get("outputValueKey").hasError("required")||n.calculateDeltaConfigForm.get("outputValueKey").hasError("pattern")),t.ɵɵadvance(3),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(17,23,"tb.rulenode.number-of-digits-after-floating-point")),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",n.calculateDeltaConfigForm.get("round").hasError("min")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.calculateDeltaConfigForm.get("round").hasError("max")),t.ɵɵadvance(2),t.ɵɵpropertyInterpolate("tb-hint-tooltip-icon",t.ɵɵpipeBind1(23,25,"tb.rulenode.failure-if-delta-negative-tooltip")),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(26,27,"tb.rulenode.failure-if-delta-negative")," "),t.ɵɵadvance(2),t.ɵɵpropertyInterpolate("tb-hint-tooltip-icon",t.ɵɵpipeBind2(29,31,"tb.rulenode.use-caching-tooltip",t.ɵɵpureFunction1(58,Si,n.calculateDeltaConfigForm.get("inputValueKey").valid?n.calculateDeltaConfigForm.get("inputValueKey").value:t.ɵɵpipeBind1(28,29,"tb.rulenode.input-value-key")))),t.ɵɵadvance(4),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(32,34,"tb.rulenode.use-caching")," "),t.ɵɵadvance(2),t.ɵɵclassProp("no-padding-bottom",n.calculateDeltaConfigForm.get("addPeriodBetweenMsgs").value),t.ɵɵadvance(),t.ɵɵpropertyInterpolate("tb-hint-tooltip-icon",t.ɵɵpipeBind2(35,36,"tb.rulenode.add-time-difference-between-readings-tooltip",t.ɵɵpureFunction1(60,Ti,n.calculateDeltaConfigForm.get("periodValueKey").valid&&n.calculateDeltaConfigForm.get("addPeriodBetweenMsgs").value?n.calculateDeltaConfigForm.get("periodValueKey").value:"periodInMs"))),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind2(39,41,"tb.rulenode.add-time-difference-between-readings",t.ɵɵpureFunction1(62,Si,n.calculateDeltaConfigForm.get("inputValueKey").valid?n.calculateDeltaConfigForm.get("inputValueKey").value:t.ɵɵpipeBind1(38,39,"tb.rulenode.input-value-key")))," "),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",n.calculateDeltaConfigForm.get("addPeriodBetweenMsgs").value),t.ɵɵadvance(),t.ɵɵpropertyInterpolate("tb-hint-tooltip-icon",n.calculateDeltaConfigForm.get("addPeriodBetweenMsgs").value?t.ɵɵpipeBind2(44,48,"tb.rulenode.exclude-zero-deltas-time-difference-hint",t.ɵɵpureFunction2(64,Ii,n.calculateDeltaConfigForm.get("outputValueKey").valid?n.calculateDeltaConfigForm.get("outputValueKey").value:t.ɵɵpipeBind1(42,44,"tb.rulenode.output-value-key"),n.calculateDeltaConfigForm.get("periodValueKey").valid?n.calculateDeltaConfigForm.get("periodValueKey").value:t.ɵɵpipeBind1(43,46,"tb.rulenode.period-value-key"))):t.ɵɵpipeBind2(46,53,"tb.rulenode.exclude-zero-deltas-hint",t.ɵɵpureFunction1(67,Ei,n.calculateDeltaConfigForm.get("outputValueKey").valid?n.calculateDeltaConfigForm.get("outputValueKey").value:t.ɵɵpipeBind1(45,51,"tb.rulenode.output-value-key")))),t.ɵɵadvance(7),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(49,56,"tb.rulenode.exclude-zero-deltas")," "))},dependencies:t.ɵɵgetComponentDepsFactory(Mi),encapsulation:2})}}function Bi(e,n){if(1&e&&(t.ɵɵelementStart(0,"tb-toggle-option",8),t.ɵɵtext(1),t.ɵɵelementEnd()),2&e){const e=n.$implicit;t.ɵɵproperty("value",e.value),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",e.name," ")}}e("CalculateDeltaConfigComponent",Mi);class Vi extends i{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.fetchToData=[],this.DataToFetch=Ft;for(const e of qt.keys())e!==Ft.FIELDS&&this.fetchToData.push({value:e,name:this.translate.instant(qt.get(e))})}configForm(){return this.customerAttributesConfigForm}prepareOutputConfig(e){const t={};for(const n of Object.keys(e.dataMapping))t[n.trim()]=e.dataMapping[n];return e.dataMapping=t,_(e)}prepareInputConfig(e){let t,n;return t=P(e?.telemetry)?e.telemetry?Ft.LATEST_TELEMETRY:Ft.ATTRIBUTES:P(e?.dataToFetch)?e.dataToFetch:Ft.ATTRIBUTES,n=P(e?.attrMapping)?e.attrMapping:P(e?.dataMapping)?e.dataMapping:null,{dataToFetch:t,dataMapping:n,fetchTo:P(e?.fetchTo)?e.fetchTo:Kt.METADATA}}selectTranslation(e,t){return this.customerAttributesConfigForm.get("dataToFetch").value===Ft.LATEST_TELEMETRY?e:t}onConfigurationSet(e){this.customerAttributesConfigForm=this.fb.group({dataToFetch:[e.dataToFetch,[]],dataMapping:[e.dataMapping,[N.required]],fetchTo:[e.fetchTo]})}static{this.ɵfac=function(e){return new(e||Vi)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.FormBuilder),t.ɵɵdirectiveInject(K.TranslateService))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Vi,selectors:[["tb-enrichment-node-customer-attributes-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:17,vars:26,consts:[[1,"tb-form-panel","stroked",3,"formGroup"],["translate","",1,"tb-form-panel-title"],[1,"flex","flex-1","items-center","justify-center"],[1,"fetch-to-data-toggle"],["formControlName","dataToFetch","appearance","fill",1,"fetch-to-data-toggle"],[3,"value",4,"ngFor","ngForOf"],["required","","formControlName","dataMapping","popupHelpLink","rulenode/customer_attributes_node_fields_templatization",3,"requiredText","labelText","keyText","keyRequiredText","valText","valRequiredText","hintText"],["formControlName","fetchTo",3,"labelText"],[3,"value"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"div",1),t.ɵɵtext(2,"tb.rulenode.mapping-of-customers"),t.ɵɵelementEnd(),t.ɵɵelementStart(3,"div",2)(4,"div",3)(5,"tb-toggle-select",4),t.ɵɵtemplate(6,Bi,2,2,"tb-toggle-option",5),t.ɵɵelementEnd()()(),t.ɵɵelement(7,"tb-kv-map-config",6),t.ɵɵpipe(8,"translate"),t.ɵɵpipe(9,"translate"),t.ɵɵpipe(10,"translate"),t.ɵɵpipe(11,"translate"),t.ɵɵpipe(12,"translate"),t.ɵɵpipe(13,"translate"),t.ɵɵelement(14,"tb-msg-metadata-chip",7),t.ɵɵpipe(15,"translate"),t.ɵɵpipe(16,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵproperty("formGroup",n.customerAttributesConfigForm),t.ɵɵadvance(6),t.ɵɵproperty("ngForOf",n.fetchToData),t.ɵɵadvance(),t.ɵɵproperty("requiredText",t.ɵɵpipeBind1(8,10,"tb.rulenode.attr-mapping-required"))("labelText",t.ɵɵpipeBind1(9,12,n.selectTranslation("tb.rulenode.latest-telemetry-mapping","tb.rulenode.attributes-mapping")))("keyText",t.ɵɵpipeBind1(10,14,n.selectTranslation("tb.rulenode.source-telemetry","tb.rulenode.source-attribute")))("keyRequiredText",t.ɵɵpipeBind1(11,16,n.selectTranslation("tb.rulenode.source-telemetry-required","tb.rulenode.source-attribute-required")))("valText",t.ɵɵpipeBind1(12,18,"tb.rulenode.target-key"))("valRequiredText",t.ɵɵpipeBind1(13,20,"tb.rulenode.target-key-required"))("hintText","tb.rulenode.kv-map-pattern-hint"),t.ɵɵadvance(7),t.ɵɵproperty("labelText",n.customerAttributesConfigForm.get("dataToFetch").value===n.DataToFetch.LATEST_TELEMETRY?t.ɵɵpipeBind1(15,22,"tb.rulenode.add-mapped-latest-telemetry-to"):t.ɵɵpipeBind1(16,24,"tb.rulenode.add-mapped-attribute-to")))},dependencies:t.ɵɵgetComponentDepsFactory(Vi),styles:["[_nghost-%COMP%] .fetch-to-data-toggle[_ngcontent-%COMP%]{max-width:420px;width:100%}"]})}}e("CustomerAttributesConfigComponent",Vi);class Oi extends i{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n}configForm(){return this.deviceAttributesConfigForm}onConfigurationSet(e){this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e.deviceRelationsQuery,[N.required]],tellFailureIfAbsent:[e.tellFailureIfAbsent,[]],fetchTo:[e.fetchTo,[]],attributesControl:[e.attributesControl,[]]})}prepareInputConfig(e){return j(e)&&(e.attributesControl={clientAttributeNames:P(e?.clientAttributeNames)?e.clientAttributeNames:[],latestTsKeyNames:P(e?.latestTsKeyNames)?e.latestTsKeyNames:[],serverAttributeNames:P(e?.serverAttributeNames)?e.serverAttributeNames:[],sharedAttributeNames:P(e?.sharedAttributeNames)?e.sharedAttributeNames:[],getLatestValueWithTs:!!P(e?.getLatestValueWithTs)&&e.getLatestValueWithTs}),{deviceRelationsQuery:P(e?.deviceRelationsQuery)?e.deviceRelationsQuery:null,tellFailureIfAbsent:!P(e?.tellFailureIfAbsent)||e.tellFailureIfAbsent,fetchTo:P(e?.fetchTo)?e.fetchTo:Kt.METADATA,attributesControl:e?e.attributesControl:null}}prepareOutputConfig(e){for(const t of Object.keys(e.attributesControl))e[t]=e.attributesControl[t];return delete e.attributesControl,e}static{this.ɵfac=function(e){return new(e||Oi)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(K.TranslateService),t.ɵɵdirectiveInject(k.FormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Oi,selectors:[["tb-enrichment-node-device-attributes-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:19,vars:11,consts:[[1,"tb-form-panel","no-padding","no-border",3,"formGroup"],[1,"tb-form-panel","stroked","no-padding-bottom"],["translate","",1,"tb-form-panel-title"],["required","","formControlName","deviceRelationsQuery"],[1,"tb-form-panel","stroked"],[1,"tb-form-row","no-padding","no-border","space-between"],["translate","",1,"tb-form-panel-title","tb-required"],["translate","",1,"tb-form-panel-hint","tb-error",3,"hidden"],["formControlName","attributesControl","popupHelpLink","rulenode/related_device_attributes_node_fields_templatization"],["formControlName","fetchTo",3,"labelText"],[1,"tb-form-row","same-padding",3,"tb-hint-tooltip-icon"],["formControlName","tellFailureIfAbsent",1,"mat-slide","margin"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"div",1)(2,"div",2),t.ɵɵtext(3,"tb.rulenode.device-relations-query"),t.ɵɵelementEnd(),t.ɵɵelement(4,"tb-device-relations-query-config",3),t.ɵɵelementEnd(),t.ɵɵelementStart(5,"div",4)(6,"div",5)(7,"div",6),t.ɵɵtext(8,"tb.rulenode.related-device-attributes"),t.ɵɵelementEnd(),t.ɵɵelementStart(9,"div",7),t.ɵɵtext(10," tb.rulenode.at-least-one-field-required "),t.ɵɵelementEnd()(),t.ɵɵelement(11,"tb-select-attributes",8)(12,"tb-msg-metadata-chip",9),t.ɵɵpipe(13,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(14,"div",10),t.ɵɵpipe(15,"translate"),t.ɵɵelementStart(16,"mat-slide-toggle",11),t.ɵɵtext(17),t.ɵɵpipe(18,"translate"),t.ɵɵelementEnd()()()),2&e&&(t.ɵɵproperty("formGroup",n.deviceAttributesConfigForm),t.ɵɵadvance(9),t.ɵɵproperty("hidden",!(n.deviceAttributesConfigForm.get("attributesControl").touched&&n.deviceAttributesConfigForm.get("attributesControl").hasError("atLeastOneRequired"))),t.ɵɵadvance(3),t.ɵɵproperty("labelText",t.ɵɵpipeBind1(13,5,"tb.rulenode.add-selected-attributes-to")),t.ɵɵadvance(2),t.ɵɵpropertyInterpolate("tb-hint-tooltip-icon",t.ɵɵpipeBind1(15,7,"tb.rulenode.tell-failure-tooltip")),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(18,9,"tb.rulenode.tell-failure")," "))},dependencies:t.ɵɵgetComponentDepsFactory(Oi),encapsulation:2})}}e("DeviceAttributesConfigComponent",Oi);const Di=e=>({inputName:e});class Li extends i{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n,this.predefinedValues=[];for(const e of Object.keys(xt))this.predefinedValues.push({value:xt[e],name:this.translate.instant(Ct.get(xt[e]))})}ngOnInit(){super.ngOnInit()}configForm(){return this.entityDetailsConfigForm}prepareInputConfig(e){let t;return t=P(e?.addToMetadata)?e.addToMetadata?Kt.METADATA:Kt.DATA:e?.fetchTo?e.fetchTo:Kt.DATA,{detailsList:P(e?.detailsList)?e.detailsList:null,fetchTo:t}}onConfigurationSet(e){this.entityDetailsConfigForm=this.fb.group({detailsList:[e.detailsList,[N.required]],fetchTo:[e.fetchTo,[]]})}static{this.ɵfac=function(e){return new(e||Li)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(K.TranslateService),t.ɵɵdirectiveInject(k.FormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Li,selectors:[["tb-enrichment-node-entity-details-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:11,vars:22,consts:[[3,"formGroup"],["required","","formControlName","detailsList",1,"mat-block",3,"predefinedValues","label","placeholder","requiredText"],["matSuffix","","aria-hidden","false","aria-label","help-icon","color","primary",1,"help-icon","margin-8","cursor-pointer",3,"matTooltip"],["formControlName","fetchTo",3,"labelText"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"tb-string-items-list",1),t.ɵɵpipe(2,"translate"),t.ɵɵpipe(3,"translate"),t.ɵɵpipe(4,"translate"),t.ɵɵelementStart(5,"mat-icon",2),t.ɵɵpipe(6,"translate"),t.ɵɵpipe(7,"translate"),t.ɵɵtext(8," help "),t.ɵɵelementEnd()(),t.ɵɵelement(9,"tb-msg-metadata-chip",3),t.ɵɵpipe(10,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵproperty("formGroup",n.entityDetailsConfigForm),t.ɵɵadvance(),t.ɵɵproperty("predefinedValues",n.predefinedValues)("label",t.ɵɵpipeBind1(2,7,"tb.rulenode.select-details"))("placeholder",t.ɵɵpipeBind1(3,9,"tb.rulenode.add-detail"))("requiredText",t.ɵɵpipeBind1(4,11,"tb.rulenode.entity-details-list-empty")),t.ɵɵadvance(4),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind2(7,15,"tb.rulenode.chip-help",t.ɵɵpureFunction1(20,Di,t.ɵɵpipeBind1(6,13,"tb.rulenode.detail")))),t.ɵɵadvance(4),t.ɵɵproperty("labelText",t.ɵɵpipeBind1(10,18,"tb.rulenode.add-selected-details-to")))},dependencies:t.ɵɵgetComponentDepsFactory(Li),encapsulation:2})}}e("EntityDetailsConfigComponent",Li);const Pi=()=>({maxWidth:"820px"}),Ri=e=>({inputName:e}),_i=(e,t,n,r)=>({startInterval:e,endInterval:t,startIntervalTimeUnit:n,endIntervalTimeUnit:r});function ji(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.start-interval-value-required")," "))}function Gi(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.time-value-range")," "))}function Ki(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.time-value-range")," "))}function Ui(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",29),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=n.$implicit,r=t.ɵɵnextContext(2);t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,2,r.timeUnitsTranslationMap.get(e))," ")}}function Hi(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.end-interval-value-required")," "))}function zi(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.time-value-range")," "))}function $i(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.time-value-range")," "))}function Qi(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",29),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=n.$implicit,r=t.ɵɵnextContext(2);t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,2,r.timeUnitsTranslationMap.get(e))," ")}}function Ji(e,n){if(1&e&&(t.ɵɵelementContainerStart(0),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementContainerEnd()),2&e){const e=t.ɵɵnextContext(2);t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind2(2,1,"tb.rulenode.fetch-timeseries-from-to",t.ɵɵpureFunction4(4,_i,e.getTelemetryFromDatabaseConfigForm.get("interval.startInterval").value,e.getTelemetryFromDatabaseConfigForm.get("interval.endInterval").value,e.getTelemetryFromDatabaseConfigForm.get("interval.startIntervalTimeUnit").value.toLowerCase(),e.getTelemetryFromDatabaseConfigForm.get("interval.endIntervalTimeUnit").value.toLowerCase()))," ")}}function Yi(e,n){1&e&&(t.ɵɵtext(0),t.ɵɵpipe(1,"translate")),2&e&&t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(1,1,"tb.rulenode.fetch-timeseries-from-to-invalid")," ")}function Wi(e,n){if(1&e&&(t.ɵɵelementStart(0,"div",17)(1,"div",18)(2,"mat-form-field",19)(3,"mat-label"),t.ɵɵtext(4),t.ɵɵpipe(5,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(6,"input",20),t.ɵɵtemplate(7,ji,3,3,"mat-error",16)(8,Gi,3,3,"mat-error",16)(9,Ki,3,3,"mat-error",16),t.ɵɵelementEnd(),t.ɵɵelementStart(10,"mat-form-field",21)(11,"mat-label"),t.ɵɵtext(12),t.ɵɵpipe(13,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(14,"mat-select",22),t.ɵɵtemplate(15,Ui,3,4,"mat-option",14),t.ɵɵelementEnd()()(),t.ɵɵelementStart(16,"div",18)(17,"mat-form-field",19)(18,"mat-label"),t.ɵɵtext(19),t.ɵɵpipe(20,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(21,"input",23),t.ɵɵtemplate(22,Hi,3,3,"mat-error",16)(23,zi,3,3,"mat-error",16)(24,$i,3,3,"mat-error",16),t.ɵɵelementEnd(),t.ɵɵelementStart(25,"mat-form-field",21)(26,"mat-label"),t.ɵɵtext(27),t.ɵɵpipe(28,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(29,"mat-select",24),t.ɵɵtemplate(30,Qi,3,4,"mat-option",14),t.ɵɵelementEnd()()(),t.ɵɵelementStart(31,"div",25)(32,"mat-icon",26),t.ɵɵtext(33,"error_outline"),t.ɵɵelementEnd(),t.ɵɵelementStart(34,"div",27),t.ɵɵtemplate(35,Ji,3,9,"ng-container",28)(36,Yi,2,3,"ng-template",null,1,t.ɵɵtemplateRefExtractor),t.ɵɵelementEnd()()()),2&e){const e=t.ɵɵreference(37),n=t.ɵɵnextContext();t.ɵɵadvance(4),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(5,16,"tb.rulenode.interval-start")),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",n.getTelemetryFromDatabaseConfigForm.get("interval.startInterval").hasError("required")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.getTelemetryFromDatabaseConfigForm.get("interval.startInterval").hasError("min")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.getTelemetryFromDatabaseConfigForm.get("interval.startInterval").hasError("max")),t.ɵɵadvance(3),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(13,18,"tb.rulenode.time-unit")),t.ɵɵadvance(3),t.ɵɵproperty("ngForOf",n.timeUnits),t.ɵɵadvance(4),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(20,20,"tb.rulenode.interval-end")),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",n.getTelemetryFromDatabaseConfigForm.get("interval.endInterval").hasError("required")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.getTelemetryFromDatabaseConfigForm.get("interval.endInterval").hasError("min")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.getTelemetryFromDatabaseConfigForm.get("interval.endInterval").hasError("max")),t.ɵɵadvance(3),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(28,22,"tb.rulenode.time-unit")),t.ɵɵadvance(3),t.ɵɵproperty("ngForOf",n.timeUnits),t.ɵɵadvance(),t.ɵɵclassProp("error",n.getTelemetryFromDatabaseConfigForm.get("interval").invalid),t.ɵɵadvance(4),t.ɵɵproperty("ngIf",n.getTelemetryFromDatabaseConfigForm.get("interval").valid)("ngIfElse",e)}}function Xi(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.start-interval-required")," "))}function Zi(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.end-interval-required")," "))}function eo(e,n){if(1&e&&(t.ɵɵelementStart(0,"div",30)(1,"mat-form-field",31)(2,"mat-label"),t.ɵɵtext(3),t.ɵɵpipe(4,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(5,"input",32),t.ɵɵtemplate(6,Xi,3,3,"mat-error",16),t.ɵɵelementEnd(),t.ɵɵelementStart(7,"mat-form-field",31)(8,"mat-label"),t.ɵɵtext(9),t.ɵɵpipe(10,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(11,"input",33),t.ɵɵtemplate(12,Zi,3,3,"mat-error",16),t.ɵɵelementEnd(),t.ɵɵelement(13,"tb-example-hint",34),t.ɵɵpipe(14,"translate"),t.ɵɵelementEnd()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(3),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(4,5,"tb.rulenode.start-interval")),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",e.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").hasError("required")||e.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").hasError("pattern")),t.ɵɵadvance(3),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(10,7,"tb.rulenode.end-interval")),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",e.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").hasError("required")||e.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").hasError("pattern")),t.ɵɵadvance(),t.ɵɵproperty("hintText",t.ɵɵpipeBind1(14,9,"tb.rulenode.metadata-dynamic-interval-hint"))}}function to(e,n){if(1&e&&(t.ɵɵelementStart(0,"tb-toggle-option",29),t.ɵɵtext(1),t.ɵɵelementEnd()),2&e){const e=n.$implicit;t.ɵɵproperty("value",e.value),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",e.name," ")}}function no(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",29),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=n.$implicit,r=t.ɵɵnextContext(2);t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,2,r.aggregationTypesTranslations.get(r.aggregationTypes[e]))," ")}}function ro(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",29),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=n.$implicit,r=t.ɵɵnextContext(3);t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,2,r.samplingOrdersTranslate.get(e))," ")}}function ao(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.limit-required")," "))}function io(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.limit-range")," "))}function oo(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.limit-range")," "))}function lo(e,n){if(1&e&&(t.ɵɵelementStart(0,"div")(1,"mat-form-field",37)(2,"mat-label"),t.ɵɵtext(3),t.ɵɵpipe(4,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(5,"mat-select",38),t.ɵɵtemplate(6,ro,3,4,"mat-option",14),t.ɵɵelementEnd()(),t.ɵɵelementStart(7,"mat-form-field",39)(8,"mat-label"),t.ɵɵtext(9),t.ɵɵpipe(10,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(11,"input",40),t.ɵɵelementStart(12,"mat-hint"),t.ɵɵtext(13),t.ɵɵpipe(14,"translate"),t.ɵɵelementEnd(),t.ɵɵtemplate(15,ao,3,3,"mat-error",16)(16,io,3,3,"mat-error",16)(17,oo,3,3,"mat-error",16),t.ɵɵelementEnd()()),2&e){const e=t.ɵɵnextContext(2);t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(4,7,"tb.rulenode.order-by-timestamp")," "),t.ɵɵadvance(3),t.ɵɵproperty("ngForOf",e.samplingOrders),t.ɵɵadvance(3),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(10,9,"tb.rulenode.limit")),t.ɵɵadvance(4),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(14,11,"tb.rulenode.limit-hint")),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",e.getTelemetryFromDatabaseConfigForm.get("limit").hasError("required")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",e.getTelemetryFromDatabaseConfigForm.get("limit").hasError("min")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",e.getTelemetryFromDatabaseConfigForm.get("limit").hasError("max"))}}function so(e,n){if(1&e&&(t.ɵɵelementStart(0,"div")(1,"mat-form-field",35)(2,"mat-label"),t.ɵɵtext(3),t.ɵɵpipe(4,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(5,"mat-select",36),t.ɵɵtemplate(6,no,3,4,"mat-option",14),t.ɵɵelementEnd()(),t.ɵɵtemplate(7,lo,18,13,"div",16),t.ɵɵelementEnd()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(),t.ɵɵproperty("subscriptSizing",e.defaultPaddingEnable()?"fixed":"dynamic"),t.ɵɵadvance(2),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(4,4,"aggregation.function")),t.ɵɵadvance(3),t.ɵɵproperty("ngForOf",e.aggregations),t.ɵɵadvance(),t.ɵɵproperty("ngIf",e.getTelemetryFromDatabaseConfigForm.get("aggregation").value===e.aggregationTypes.NONE)}}class po extends i{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n,this.separatorKeysCodes=[U,H,z],this.aggregationTypes=T,this.aggregations=Object.values(T),this.aggregationTypesTranslations=I,this.fetchMode=St,this.samplingOrders=Object.values(Et),this.samplingOrdersTranslate=kt,this.timeUnits=Object.values(ht),this.timeUnitsTranslationMap=yt,this.deduplicationStrategiesHintTranslations=It,this.headerOptions=[],this.timeUnitMap={[ht.MILLISECONDS]:1,[ht.SECONDS]:1e3,[ht.MINUTES]:6e4,[ht.HOURS]:36e5,[ht.DAYS]:864e5},this.intervalValidator=()=>e=>e.get("startInterval").value*this.timeUnitMap[e.get("startIntervalTimeUnit").value]<=e.get("endInterval").value*this.timeUnitMap[e.get("endIntervalTimeUnit").value]?{intervalError:!0}:null;for(const e of Tt.keys())this.headerOptions.push({value:e,name:this.translate.instant(Tt.get(e))})}configForm(){return this.getTelemetryFromDatabaseConfigForm}onConfigurationSet(e){this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e.latestTsKeyNames,[N.required]],aggregation:[e.aggregation,[N.required]],fetchMode:[e.fetchMode,[N.required]],orderBy:[e.orderBy,[]],limit:[e.limit,[]],useMetadataIntervalPatterns:[e.useMetadataIntervalPatterns,[]],interval:this.fb.group({startInterval:[e.interval.startInterval,[]],startIntervalTimeUnit:[e.interval.startIntervalTimeUnit,[]],endInterval:[e.interval.endInterval,[]],endIntervalTimeUnit:[e.interval.endIntervalTimeUnit,[]]}),startIntervalPattern:[e.startIntervalPattern,[]],endIntervalPattern:[e.endIntervalPattern,[]]})}validatorTriggers(){return["fetchMode","useMetadataIntervalPatterns"]}toggleChange(e){this.getTelemetryFromDatabaseConfigForm.get("fetchMode").patchValue(e,{emitEvent:!0})}prepareOutputConfig(e){return e.startInterval=e.interval.startInterval,e.startIntervalTimeUnit=e.interval.startIntervalTimeUnit,e.endInterval=e.interval.endInterval,e.endIntervalTimeUnit=e.interval.endIntervalTimeUnit,delete e.interval,_(e)}prepareInputConfig(e){return j(e)&&(e.interval={startInterval:e.startInterval,startIntervalTimeUnit:e.startIntervalTimeUnit,endInterval:e.endInterval,endIntervalTimeUnit:e.endIntervalTimeUnit}),{latestTsKeyNames:P(e?.latestTsKeyNames)?e.latestTsKeyNames:null,aggregation:P(e?.aggregation)?e.aggregation:T.NONE,fetchMode:P(e?.fetchMode)?e.fetchMode:St.FIRST,orderBy:P(e?.orderBy)?e.orderBy:Et.ASC,limit:P(e?.limit)?e.limit:1e3,useMetadataIntervalPatterns:!!P(e?.useMetadataIntervalPatterns)&&e.useMetadataIntervalPatterns,interval:{startInterval:P(e?.interval?.startInterval)?e.interval.startInterval:2,startIntervalTimeUnit:P(e?.interval?.startIntervalTimeUnit)?e.interval.startIntervalTimeUnit:ht.MINUTES,endInterval:P(e?.interval?.endInterval)?e.interval.endInterval:1,endIntervalTimeUnit:P(e?.interval?.endIntervalTimeUnit)?e.interval.endIntervalTimeUnit:ht.MINUTES},startIntervalPattern:P(e?.startIntervalPattern)?e.startIntervalPattern:null,endIntervalPattern:P(e?.endIntervalPattern)?e.endIntervalPattern:null}}updateValidators(e){const t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,n=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===St.ALL?(this.getTelemetryFromDatabaseConfigForm.get("aggregation").setValidators([N.required]),this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([N.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([N.required,N.min(2),N.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("aggregation").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),n?(this.getTelemetryFromDatabaseConfigForm.get("interval.startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("interval.startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("interval.endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("interval.endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("interval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([N.required,N.pattern(/(?:.|\s)*\S(&:.|\s)*/)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([N.required,N.pattern(/(?:.|\s)*\S(&:.|\s)*/)])):(this.getTelemetryFromDatabaseConfigForm.get("interval.startInterval").setValidators([N.required,N.min(1),N.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("interval.startIntervalTimeUnit").setValidators([N.required]),this.getTelemetryFromDatabaseConfigForm.get("interval.endInterval").setValidators([N.required,N.min(1),N.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("interval.endIntervalTimeUnit").setValidators([N.required]),this.getTelemetryFromDatabaseConfigForm.get("interval").setValidators([this.intervalValidator()]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("aggregation").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("interval.startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("interval.startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("interval.endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("interval.endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("interval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})}removeKey(e,t){const n=this.getTelemetryFromDatabaseConfigForm.get(t).value,r=n.indexOf(e);r>=0&&(n.splice(r,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(n,{emitEvent:!0}))}clearChipGrid(){this.getTelemetryFromDatabaseConfigForm.get("latestTsKeyNames").patchValue([],{emitEvent:!0})}addKey(e,t){const n=e.input;let r=e.value;if((r||"").trim()){r=r.trim();let e=this.getTelemetryFromDatabaseConfigForm.get(t).value;e&&-1!==e.indexOf(r)||(e||(e=[]),e.push(r),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(e,{emitEvent:!0}))}n&&(n.value="")}defaultPaddingEnable(){return this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value===St.ALL&&this.getTelemetryFromDatabaseConfigForm.get("aggregation").value===T.NONE}static{this.ɵfac=function(e){return new(e||po)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(K.TranslateService),t.ɵɵdirectiveInject(k.FormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:po,selectors:[["tb-enrichment-node-get-telemetry-from-database"]],features:[t.ɵɵInheritDefinitionFeature],decls:34,vars:40,consts:[["intervalPattern",""],["invalidText",""],[1,"tb-form-panel","no-padding","no-border",3,"formGroup"],["editable","","subscriptSizing","dynamic","required","","formControlName","latestTsKeyNames",1,"mat-block",3,"placeholder","requiredText","label","hint"],["matHintEnd","","hintMode","","tb-help-popup-placement","right","trigger-style","letter-spacing:0.25px; font-size:12px",1,"see-example",3,"tb-help-popup","tb-help-popup-style","trigger-text"],["matSuffix","","aria-hidden","false","aria-label","help-icon","color","primary",1,"help-icon","margin-8","cursor-pointer",3,"matTooltip"],[1,"tb-form-panel","stroked"],["translate","",1,"tb-form-panel-title"],[1,"tb-form-row","no-border","no-padding",3,"tb-hint-tooltip-icon"],["formControlName","useMetadataIntervalPatterns",1,"mat-slide"],["formGroupName","interval","class","flex flex-col",4,"ngIf","ngIfElse"],[1,"tb-form-panel","no-border","no-padding","item-center"],[1,"fetch-mod-toggle"],["formControlName","fetchMode","appearance","fill"],[3,"value",4,"ngFor","ngForOf"],[1,"tb-form-hint","tb-primary-fill","hint-container"],[4,"ngIf"],["formGroupName","interval",1,"flex","flex-col"],[1,"flex","flex-col","gap-0","gt-sm:flex-row","gt-sm:gap-4"],[1,"mat-block","gt-sm:max-w-50%","gt-sm:flex-full"],["type","number","step","1","min","1","max","2147483647","matInput","","formControlName","startInterval","required",""],["hideRequiredMarker","",1,"mat-block","gt-sm:max-w-50%","gt-sm:flex-full"],["formControlName","startIntervalTimeUnit","required",""],["type","number","step","1","min","1","max","2147483647","matInput","","formControlName","endInterval","required",""],["formControlName","endIntervalTimeUnit","required",""],[1,"description-block","tb-primary-fill"],[1,"description-icon"],[1,"description-text"],[4,"ngIf","ngIfElse"],[3,"value"],[1,"input-block","flex","flex-col"],[1,"mat-block","flex-1"],["matInput","","formControlName","startIntervalPattern","required",""],["matInput","","formControlName","endIntervalPattern","required",""],["popupHelpLink","rulenode/originator_telemetry_node_fields_templatization",3,"hintText"],["hideRequiredMarker","",1,"mat-block",3,"subscriptSizing"],["formControlName","aggregation","required",""],["hideRequiredMarker","",1,"mat-block"],["formControlName","orderBy","required",""],[1,"mat-block"],["type","number","min","2","max","1000","step","1","matInput","","formControlName","limit","required",""]],template:function(e,n){if(1&e&&(t.ɵɵelementStart(0,"section",2)(1,"tb-string-items-list",3),t.ɵɵpipe(2,"translate"),t.ɵɵpipe(3,"translate"),t.ɵɵpipe(4,"translate"),t.ɵɵpipe(5,"translate"),t.ɵɵelement(6,"div",4),t.ɵɵpipe(7,"translate"),t.ɵɵelementStart(8,"mat-icon",5),t.ɵɵpipe(9,"translate"),t.ɵɵpipe(10,"translate"),t.ɵɵtext(11,"help "),t.ɵɵelementEnd()(),t.ɵɵelementStart(12,"div",6)(13,"div",7),t.ɵɵtext(14,"tb.rulenode.fetch-interval"),t.ɵɵelementEnd(),t.ɵɵelementStart(15,"div",8),t.ɵɵpipe(16,"translate"),t.ɵɵelementStart(17,"mat-slide-toggle",9),t.ɵɵtext(18),t.ɵɵpipe(19,"translate"),t.ɵɵelementEnd()(),t.ɵɵtemplate(20,Wi,38,24,"div",10)(21,eo,15,11,"ng-template",null,0,t.ɵɵtemplateRefExtractor),t.ɵɵelementEnd(),t.ɵɵelementStart(23,"div",6)(24,"div",7),t.ɵɵtext(25,"tb.rulenode.fetch-strategy"),t.ɵɵelementEnd(),t.ɵɵelementStart(26,"div",11)(27,"div",12)(28,"tb-toggle-select",13),t.ɵɵtemplate(29,to,2,2,"tb-toggle-option",14),t.ɵɵelementEnd()(),t.ɵɵelementStart(30,"div",15),t.ɵɵtext(31),t.ɵɵpipe(32,"translate"),t.ɵɵelementEnd()(),t.ɵɵtemplate(33,so,8,6,"div",16),t.ɵɵelementEnd()()),2&e){const e=t.ɵɵreference(22);t.ɵɵproperty("formGroup",n.getTelemetryFromDatabaseConfigForm),t.ɵɵadvance(),t.ɵɵproperty("placeholder",t.ɵɵpipeBind1(2,16,"tb.rulenode.add-timeseries-key"))("requiredText",t.ɵɵpipeBind1(3,18,"tb.rulenode.timeseries-keys-required"))("label",t.ɵɵpipeBind1(4,20,"tb.rulenode.timeseries-keys"))("hint",t.ɵɵpipeBind1(5,22,"tb.rulenode.general-pattern-hint")),t.ɵɵadvance(5),t.ɵɵpropertyInterpolate("tb-help-popup","rulenode/originator_telemetry_node_fields_templatization"),t.ɵɵpropertyInterpolate("trigger-text",t.ɵɵpipeBind1(7,24,"tb.key-val.see-examples")),t.ɵɵproperty("tb-help-popup-style",t.ɵɵpureFunction0(37,Pi)),t.ɵɵadvance(2),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind2(10,28,"tb.rulenode.chip-help",t.ɵɵpureFunction1(38,Ri,t.ɵɵpipeBind1(9,26,"tb.rulenode.timeseries-key")))),t.ɵɵadvance(7),t.ɵɵpropertyInterpolate("tb-hint-tooltip-icon",t.ɵɵpipeBind1(16,31,"tb.rulenode.use-metadata-dynamic-interval-tooltip")),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(19,33,"tb.rulenode.use-metadata-dynamic-interval")," "),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",!1===n.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value)("ngIfElse",e),t.ɵɵadvance(9),t.ɵɵproperty("ngForOf",n.headerOptions),t.ɵɵadvance(2),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(32,35,n.deduplicationStrategiesHintTranslations.get(n.getTelemetryFromDatabaseConfigForm.get("fetchMode").value))," "),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",n.getTelemetryFromDatabaseConfigForm.get("fetchMode").value===n.fetchMode.ALL)}},dependencies:t.ɵɵgetComponentDepsFactory(po),styles:["[_nghost-%COMP%] .see-example[_ngcontent-%COMP%]{display:inline-block}[_nghost-%COMP%] .description-block[_ngcontent-%COMP%]{display:flex;align-items:center;border-radius:6px;border:1px solid #EAEAEA}[_nghost-%COMP%] .description-block[_ngcontent-%COMP%] .description-icon[_ngcontent-%COMP%]{font-size:24px;height:24px;min-height:24px;width:24px;min-width:24px;line-height:24px;color:#d9d9d9;margin:4px}[_nghost-%COMP%] .description-block[_ngcontent-%COMP%] .description-text[_ngcontent-%COMP%]{font-size:12px;line-height:16px;letter-spacing:.25px;margin:6px}[_nghost-%COMP%] .description-block.error[_ngcontent-%COMP%]{color:var(--mdc-theme-error, #f44336)}[_nghost-%COMP%] .description-block.error[_ngcontent-%COMP%] .description-icon[_ngcontent-%COMP%]{color:var(--mdc-theme-error, #f44336)}[_nghost-%COMP%] .item-center[_ngcontent-%COMP%]{align-items:center}[_nghost-%COMP%] .item-center[_ngcontent-%COMP%] .fetch-mod-toggle[_ngcontent-%COMP%]{width:100%}[_nghost-%COMP%] .hint-container[_ngcontent-%COMP%]{width:100%}"]})}}e("GetTelemetryFromDatabaseConfigComponent",po);class mo extends i{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n}configForm(){return this.originatorAttributesConfigForm}onConfigurationSet(e){this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[e.tellFailureIfAbsent,[]],fetchTo:[e.fetchTo,[]],attributesControl:[e.attributesControl,[]]})}prepareInputConfig(e){return j(e)&&(e.attributesControl={clientAttributeNames:P(e?.clientAttributeNames)?e.clientAttributeNames:[],latestTsKeyNames:P(e?.latestTsKeyNames)?e.latestTsKeyNames:[],serverAttributeNames:P(e?.serverAttributeNames)?e.serverAttributeNames:[],sharedAttributeNames:P(e?.sharedAttributeNames)?e.sharedAttributeNames:[],getLatestValueWithTs:!!P(e?.getLatestValueWithTs)&&e.getLatestValueWithTs}),{fetchTo:P(e?.fetchTo)?e.fetchTo:Kt.METADATA,tellFailureIfAbsent:!!P(e?.tellFailureIfAbsent)&&e.tellFailureIfAbsent,attributesControl:P(e?.attributesControl)?e.attributesControl:null}}prepareOutputConfig(e){for(const t of Object.keys(e.attributesControl))e[t]=e.attributesControl[t];return delete e.attributesControl,e}static{this.ɵfac=function(e){return new(e||mo)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(K.TranslateService),t.ɵɵdirectiveInject(k.FormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:mo,selectors:[["tb-enrichment-node-originator-attributes-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:15,vars:11,consts:[[1,"tb-form-panel","no-padding","no-border",3,"formGroup"],[1,"tb-form-panel","stroked"],[1,"tb-form-row","no-padding","no-border","space-between"],["translate","",1,"tb-form-panel-title","tb-required"],["translate","",1,"tb-form-panel-hint","tb-error",3,"hidden"],["formControlName","attributesControl","popupHelpLink","rulenode/originator_attributes_node_fields_templatization"],["formControlName","fetchTo",3,"labelText"],[1,"tb-form-row","same-padding",3,"tb-hint-tooltip-icon"],["formControlName","tellFailureIfAbsent",1,"mat-slide","margin"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"div",1)(2,"div",2)(3,"div",3),t.ɵɵtext(4,"tb.rulenode.originator-attributes"),t.ɵɵelementEnd(),t.ɵɵelementStart(5,"div",4),t.ɵɵtext(6," tb.rulenode.at-least-one-field-required "),t.ɵɵelementEnd()(),t.ɵɵelement(7,"tb-select-attributes",5)(8,"tb-msg-metadata-chip",6),t.ɵɵpipe(9,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(10,"div",7),t.ɵɵpipe(11,"translate"),t.ɵɵelementStart(12,"mat-slide-toggle",8),t.ɵɵtext(13),t.ɵɵpipe(14,"translate"),t.ɵɵelementEnd()()()),2&e&&(t.ɵɵproperty("formGroup",n.originatorAttributesConfigForm),t.ɵɵadvance(5),t.ɵɵproperty("hidden",!(n.originatorAttributesConfigForm.get("attributesControl").touched&&n.originatorAttributesConfigForm.get("attributesControl").hasError("atLeastOneRequired"))),t.ɵɵadvance(3),t.ɵɵproperty("labelText",t.ɵɵpipeBind1(9,5,"tb.rulenode.add-originator-attributes-to")),t.ɵɵadvance(2),t.ɵɵpropertyInterpolate("tb-hint-tooltip-icon",t.ɵɵpipeBind1(11,7,"tb.rulenode.tell-failure-tooltip")),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(14,9,"tb.rulenode.tell-failure")," "))},dependencies:t.ɵɵgetComponentDepsFactory(mo),encapsulation:2})}}e("OriginatorAttributesConfigComponent",mo);class uo extends i{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.originatorFields=[];for(const e of ut)this.originatorFields.push({value:e.value,name:this.translate.instant(e.name)})}configForm(){return this.originatorFieldsConfigForm}prepareOutputConfig(e){return _(e)}prepareInputConfig(e){return{dataMapping:P(e?.dataMapping)?e.dataMapping:null,ignoreNullStrings:P(e?.ignoreNullStrings)?e.ignoreNullStrings:null,fetchTo:P(e?.fetchTo)?e.fetchTo:Kt.METADATA}}onConfigurationSet(e){this.originatorFieldsConfigForm=this.fb.group({dataMapping:[e.dataMapping,[N.required]],ignoreNullStrings:[e.ignoreNullStrings,[]],fetchTo:[e.fetchTo,[]]})}static{this.ɵfac=function(e){return new(e||uo)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.FormBuilder),t.ɵɵdirectiveInject(K.TranslateService))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:uo,selectors:[["tb-enrichment-node-originator-fields-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:16,vars:32,consts:[[1,"tb-form-panel","no-padding","no-border",3,"formGroup"],["required","","targetKeyPrefix","originator","formControlName","dataMapping","popupHelpLink","rulenode/originator_fields_node_fields_templatization",3,"selectOptions","requiredText","labelText","selectText","selectRequiredText","valText","valRequiredText","hintText"],["formControlName","fetchTo",3,"labelText"],[1,"tb-form-row","same-padding",3,"tb-hint-tooltip-icon"],["formControlName","ignoreNullStrings",1,"mat-slide","margin"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0),t.ɵɵelement(1,"tb-sv-map-config",1),t.ɵɵpipe(2,"translate"),t.ɵɵpipe(3,"translate"),t.ɵɵpipe(4,"translate"),t.ɵɵpipe(5,"translate"),t.ɵɵpipe(6,"translate"),t.ɵɵpipe(7,"translate"),t.ɵɵpipe(8,"translate"),t.ɵɵelement(9,"tb-msg-metadata-chip",2),t.ɵɵpipe(10,"translate"),t.ɵɵelementStart(11,"div",3),t.ɵɵpipe(12,"translate"),t.ɵɵelementStart(13,"mat-slide-toggle",4),t.ɵɵtext(14),t.ɵɵpipe(15,"translate"),t.ɵɵelementEnd()()()),2&e&&(t.ɵɵproperty("formGroup",n.originatorFieldsConfigForm),t.ɵɵadvance(),t.ɵɵproperty("selectOptions",n.originatorFields)("requiredText",t.ɵɵpipeBind1(2,12,"tb.rulenode.attr-mapping-required"))("labelText",t.ɵɵpipeBind1(3,14,"tb.rulenode.originator-fields-mapping"))("selectText",t.ɵɵpipeBind1(4,16,"tb.rulenode.source-field"))("selectRequiredText",t.ɵɵpipeBind1(5,18,"tb.rulenode.source-field-required"))("valText",t.ɵɵpipeBind1(6,20,"tb.rulenode.target-key"))("valRequiredText",t.ɵɵpipeBind1(7,22,"tb.rulenode.target-key-required"))("hintText",t.ɵɵpipeBind1(8,24,"tb.rulenode.originator-fields-sv-map-hint")),t.ɵɵadvance(8),t.ɵɵproperty("labelText",t.ɵɵpipeBind1(10,26,"tb.rulenode.add-mapped-originator-fields-to")),t.ɵɵadvance(2),t.ɵɵpropertyInterpolate("tb-hint-tooltip-icon",t.ɵɵpipeBind1(12,28,"tb.rulenode.skip-empty-fields-tooltip")),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(15,30,"tb.rulenode.skip-empty-fields")," "))},dependencies:t.ɵɵgetComponentDepsFactory(uo),encapsulation:2})}}function co(e,n){if(1&e&&(t.ɵɵelementStart(0,"tb-toggle-option",9),t.ɵɵtext(1),t.ɵɵelementEnd()),2&e){const e=n.$implicit;t.ɵɵproperty("value",e.value),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",e.name," ")}}e("OriginatorFieldsConfigComponent",uo);class fo extends i{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.DataToFetch=Ft,this.msgMetadataLabelTranslations=At,this.originatorFields=[],this.fetchToData=[];for(const e of Object.keys(ut))this.originatorFields.push({value:ut[e].value,name:this.translate.instant(ut[e].name)});for(const e of qt.keys())this.fetchToData.push({value:e,name:this.translate.instant(qt.get(e))})}configForm(){return this.relatedAttributesConfigForm}prepareOutputConfig(e){e.dataToFetch===Ft.FIELDS?(e.dataMapping=e.svMap,delete e.svMap):(e.dataMapping=e.kvMap,delete e.kvMap);const t={};if(e&&e.dataMapping)for(const n of Object.keys(e.dataMapping))t[n.trim()]=e.dataMapping[n];return e.dataMapping=t,delete e.svMap,delete e.kvMap,_(e)}prepareInputConfig(e){let t,n,r={[c.name.value]:`relatedEntity${this.translate.instant(c.name.name)}`},a={serialNumber:"sn"};return t=P(e?.telemetry)?e.telemetry?Ft.LATEST_TELEMETRY:Ft.ATTRIBUTES:P(e?.dataToFetch)?e.dataToFetch:Ft.ATTRIBUTES,n=P(e?.attrMapping)?e.attrMapping:P(e?.dataMapping)?e.dataMapping:null,t===Ft.FIELDS?r=n:a=n,{relationsQuery:P(e?.relationsQuery)?e.relationsQuery:null,dataToFetch:t,svMap:r,kvMap:a,fetchTo:P(e?.fetchTo)?e.fetchTo:Kt.METADATA}}selectTranslation(e,t){return this.relatedAttributesConfigForm.get("dataToFetch").value===Ft.LATEST_TELEMETRY?e:t}onConfigurationSet(e){this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e.relationsQuery,[N.required]],dataToFetch:[e.dataToFetch,[]],kvMap:[e.kvMap,[N.required]],svMap:[e.svMap,[N.required]],fetchTo:[e.fetchTo,[]]})}validatorTriggers(){return["dataToFetch"]}updateValidators(e){this.relatedAttributesConfigForm.get("dataToFetch").value===Ft.FIELDS?(this.relatedAttributesConfigForm.get("svMap").enable({emitEvent:!1}),this.relatedAttributesConfigForm.get("kvMap").disable({emitEvent:!1}),this.relatedAttributesConfigForm.get("svMap").updateValueAndValidity()):(this.relatedAttributesConfigForm.get("svMap").disable({emitEvent:!1}),this.relatedAttributesConfigForm.get("kvMap").enable({emitEvent:!1}),this.relatedAttributesConfigForm.get("kvMap").updateValueAndValidity())}static{this.ɵfac=function(e){return new(e||fo)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.FormBuilder),t.ɵɵdirectiveInject(K.TranslateService))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:fo,selectors:[["tb-enrichment-node-related-attributes-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:24,vars:48,consts:[[1,"tb-form-panel","no-padding","no-border",3,"formGroup"],["required","","formControlName","relationsQuery"],[1,"tb-form-panel","stroked"],["translate","",1,"tb-form-panel-title"],["formControlName","dataToFetch","appearance","fill"],[3,"value",4,"ngFor","ngForOf"],["required","","formControlName","kvMap","popupHelpLink","rulenode/related_entity_data_node_fields_templatization",3,"hidden","requiredText","labelText","keyText","keyRequiredText","valText","valRequiredText","hintText"],["required","","targetKeyPrefix","relatedEntity","formControlName","svMap","popupHelpLink","rulenode/related_entity_data_node_fields_templatization",3,"hidden","labelText","selectOptions","requiredText","selectText","selectRequiredText","valText","valRequiredText","hintText"],["formControlName","fetchTo",3,"labelText"],[3,"value"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0),t.ɵɵelement(1,"tb-relations-query-config",1),t.ɵɵelementStart(2,"div",2)(3,"div",3),t.ɵɵtext(4,"tb.rulenode.data-to-fetch"),t.ɵɵelementEnd(),t.ɵɵelementStart(5,"tb-toggle-select",4),t.ɵɵtemplate(6,co,2,2,"tb-toggle-option",5),t.ɵɵelementEnd(),t.ɵɵelement(7,"tb-kv-map-config",6),t.ɵɵpipe(8,"translate"),t.ɵɵpipe(9,"translate"),t.ɵɵpipe(10,"translate"),t.ɵɵpipe(11,"translate"),t.ɵɵpipe(12,"translate"),t.ɵɵpipe(13,"translate"),t.ɵɵelement(14,"tb-sv-map-config",7),t.ɵɵpipe(15,"translate"),t.ɵɵpipe(16,"translate"),t.ɵɵpipe(17,"translate"),t.ɵɵpipe(18,"translate"),t.ɵɵpipe(19,"translate"),t.ɵɵpipe(20,"translate"),t.ɵɵpipe(21,"translate"),t.ɵɵelement(22,"tb-msg-metadata-chip",8),t.ɵɵpipe(23,"translate"),t.ɵɵelementEnd()()),2&e&&(t.ɵɵproperty("formGroup",n.relatedAttributesConfigForm),t.ɵɵadvance(6),t.ɵɵproperty("ngForOf",n.fetchToData),t.ɵɵadvance(),t.ɵɵproperty("hidden",n.relatedAttributesConfigForm.get("dataToFetch").value===n.DataToFetch.FIELDS)("requiredText",t.ɵɵpipeBind1(8,20,"tb.rulenode.attr-mapping-required"))("labelText",t.ɵɵpipeBind1(9,22,n.selectTranslation("tb.rulenode.latest-telemetry-mapping","tb.rulenode.attributes-mapping")))("keyText",t.ɵɵpipeBind1(10,24,n.selectTranslation("tb.rulenode.source-telemetry","tb.rulenode.source-attribute")))("keyRequiredText",t.ɵɵpipeBind1(11,26,n.selectTranslation("tb.rulenode.source-telemetry-required","tb.rulenode.source-attribute-required")))("valText",t.ɵɵpipeBind1(12,28,"tb.rulenode.target-key"))("valRequiredText",t.ɵɵpipeBind1(13,30,"tb.rulenode.target-key-required"))("hintText","tb.rulenode.kv-map-pattern-hint"),t.ɵɵadvance(7),t.ɵɵproperty("hidden",n.relatedAttributesConfigForm.get("dataToFetch").value!==n.DataToFetch.FIELDS)("labelText",t.ɵɵpipeBind1(15,32,"tb.rulenode.fields-mapping"))("selectOptions",n.originatorFields)("requiredText",t.ɵɵpipeBind1(16,34,"tb.rulenode.attr-mapping-required"))("selectText",t.ɵɵpipeBind1(17,36,"tb.rulenode.source-field"))("selectRequiredText",t.ɵɵpipeBind1(18,38,"tb.rulenode.source-field-required"))("valText",t.ɵɵpipeBind1(19,40,"tb.rulenode.target-key"))("valRequiredText",t.ɵɵpipeBind1(20,42,"tb.rulenode.target-key-required"))("hintText",t.ɵɵpipeBind1(21,44,"tb.rulenode.sv-map-hint")),t.ɵɵadvance(8),t.ɵɵproperty("labelText",t.ɵɵpipeBind1(23,46,n.msgMetadataLabelTranslations.get(n.relatedAttributesConfigForm.get("dataToFetch").value))))},dependencies:t.ɵɵgetComponentDepsFactory(fo),encapsulation:2})}}function go(e,n){if(1&e&&(t.ɵɵelementStart(0,"tb-toggle-option",8),t.ɵɵtext(1),t.ɵɵelementEnd()),2&e){const e=n.$implicit;t.ɵɵproperty("value",e.value),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",e.name," ")}}e("RelatedAttributesConfigComponent",fo);class ho extends i{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.fetchToData=[],this.DataToFetch=Ft;for(const e of qt.keys())e!==Ft.FIELDS&&this.fetchToData.push({value:e,name:this.translate.instant(qt.get(e))})}configForm(){return this.tenantAttributesConfigForm}prepareInputConfig(e){let t,n;return t=P(e?.telemetry)?e.telemetry?Ft.LATEST_TELEMETRY:Ft.ATTRIBUTES:P(e?.dataToFetch)?e.dataToFetch:Ft.ATTRIBUTES,n=P(e?.attrMapping)?e.attrMapping:P(e?.dataMapping)?e.dataMapping:null,{dataToFetch:t,dataMapping:n,fetchTo:P(e?.fetchTo)?e.fetchTo:Kt.METADATA}}selectTranslation(e,t){return this.tenantAttributesConfigForm.get("dataToFetch").value===Ft.LATEST_TELEMETRY?e:t}onConfigurationSet(e){this.tenantAttributesConfigForm=this.fb.group({dataToFetch:[e.dataToFetch,[]],dataMapping:[e.dataMapping,[N.required]],fetchTo:[e.fetchTo,[]]})}static{this.ɵfac=function(e){return new(e||ho)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.FormBuilder),t.ɵɵdirectiveInject(K.TranslateService))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:ho,selectors:[["tb-enrichment-node-tenant-attributes-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:17,vars:26,consts:[[1,"tb-form-panel","stroked",3,"formGroup"],["translate","",1,"tb-form-panel-title"],[1,"flex","flex-1","items-center","justify-center"],[1,"fetch-to-data-toggle"],["formControlName","dataToFetch","appearance","fill",1,"fetch-to-data-toggle"],[3,"value",4,"ngFor","ngForOf"],["required","","formControlName","dataMapping","popupHelpLink","rulenode/tenant_attributes_node_fields_templatization",3,"requiredText","labelText","keyText","keyRequiredText","valText","valRequiredText","hintText"],["formControlName","fetchTo",3,"labelText"],[3,"value"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"div",1),t.ɵɵtext(2,"tb.rulenode.mapping-of-tenant"),t.ɵɵelementEnd(),t.ɵɵelementStart(3,"div",2)(4,"div",3)(5,"tb-toggle-select",4),t.ɵɵtemplate(6,go,2,2,"tb-toggle-option",5),t.ɵɵelementEnd()()(),t.ɵɵelement(7,"tb-kv-map-config",6),t.ɵɵpipe(8,"translate"),t.ɵɵpipe(9,"translate"),t.ɵɵpipe(10,"translate"),t.ɵɵpipe(11,"translate"),t.ɵɵpipe(12,"translate"),t.ɵɵpipe(13,"translate"),t.ɵɵelement(14,"tb-msg-metadata-chip",7),t.ɵɵpipe(15,"translate"),t.ɵɵpipe(16,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵproperty("formGroup",n.tenantAttributesConfigForm),t.ɵɵadvance(6),t.ɵɵproperty("ngForOf",n.fetchToData),t.ɵɵadvance(),t.ɵɵproperty("requiredText",t.ɵɵpipeBind1(8,10,"tb.rulenode.attr-mapping-required"))("labelText",t.ɵɵpipeBind1(9,12,n.selectTranslation("tb.rulenode.latest-telemetry-mapping","tb.rulenode.attributes-mapping")))("keyText",t.ɵɵpipeBind1(10,14,n.selectTranslation("tb.rulenode.source-telemetry","tb.rulenode.source-attribute")))("keyRequiredText",t.ɵɵpipeBind1(11,16,n.selectTranslation("tb.rulenode.source-telemetry-required","tb.rulenode.source-attribute-required")))("valText",t.ɵɵpipeBind1(12,18,"tb.rulenode.target-key"))("valRequiredText",t.ɵɵpipeBind1(13,20,"tb.rulenode.target-key-required"))("hintText","tb.rulenode.kv-map-pattern-hint"),t.ɵɵadvance(7),t.ɵɵproperty("labelText",n.tenantAttributesConfigForm.get("dataToFetch").value===n.DataToFetch.LATEST_TELEMETRY?t.ɵɵpipeBind1(15,22,"tb.rulenode.add-mapped-latest-telemetry-to"):t.ɵɵpipeBind1(16,24,"tb.rulenode.add-mapped-attribute-to")))},dependencies:t.ɵɵgetComponentDepsFactory(ho),styles:["[_nghost-%COMP%] .fetch-to-data-toggle[_ngcontent-%COMP%]{max-width:420px;width:100%}"]})}}e("TenantAttributesConfigComponent",ho);class yo extends i{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.fetchDeviceCredentialsConfigForm}prepareInputConfig(e){return{fetchTo:P(e?.fetchTo)?e.fetchTo:Kt.METADATA}}onConfigurationSet(e){this.fetchDeviceCredentialsConfigForm=this.fb.group({fetchTo:[e.fetchTo,[]]})}static{this.ɵfac=function(e){return new(e||yo)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.FormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:yo,selectors:[["tb-enrichment-node-fetch-device-credentials-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:3,vars:4,consts:[[3,"formGroup"],["formControlName","fetchTo",3,"labelText"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0),t.ɵɵelement(1,"tb-msg-metadata-chip",1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵproperty("formGroup",n.fetchDeviceCredentialsConfigForm),t.ɵɵadvance(),t.ɵɵproperty("labelText",t.ɵɵpipeBind1(2,2,"tb.rulenode.fetch-credentials-to")))},dependencies:t.ɵɵgetComponentDepsFactory(yo),encapsulation:2})}}e("FetchDeviceCredentialsConfigComponent",yo);class bo{static{this.ɵfac=function(e){return new(e||bo)}}static{this.ɵmod=t.ɵɵdefineNgModule({type:bo})}static{this.ɵinj=t.ɵɵdefineInjector({imports:[$,S,xi,Vi,Li,Oi,mo,uo,po,fo,ho,Mi,yo]})}}function vo(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.topic-required")," "))}function xo(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.hostname-required")," "))}function Co(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.device-id-required")," "))}function So(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",17),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=n.$implicit,r=t.ɵɵnextContext();t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,2,r.azureIotHubCredentialsTypeTranslationsMap.get(e))," ")}}function To(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.credentials-type-required")," "))}function Io(e,t){}function Eo(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.sas-key-required")," "))}function Fo(e,n){if(1&e){const e=t.ɵɵgetCurrentView();t.ɵɵelementStart(0,"mat-form-field",5)(1,"mat-label",2),t.ɵɵtext(2,"tb.rulenode.sas-key"),t.ɵɵelementEnd(),t.ɵɵelement(3,"input",18)(4,"tb-toggle-password",19),t.ɵɵtemplate(5,Eo,3,3,"mat-error",4),t.ɵɵelementEnd(),t.ɵɵelementStart(6,"tb-file-input",20),t.ɵɵpipe(7,"translate"),t.ɵɵpipe(8,"translate"),t.ɵɵlistener("fileNameChanged",(function(n){t.ɵɵrestoreView(e);const r=t.ɵɵnextContext();return t.ɵɵresetView(r.azureIotHubConfigForm.get("credentials.caCertFileName").setValue(n))})),t.ɵɵelementEnd()}if(2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(5),t.ɵɵproperty("ngIf",e.azureIotHubConfigForm.get("credentials.sasKey").hasError("required")),t.ɵɵadvance(),t.ɵɵpropertyInterpolate("label",t.ɵɵpipeBind1(7,4,"tb.rulenode.azure-ca-cert")),t.ɵɵpropertyInterpolate("dropLabel",t.ɵɵpipeBind1(8,6,"tb.rulenode.drop-file")),t.ɵɵproperty("existingFileName",e.azureIotHubConfigForm.get("credentials.caCertFileName").value)}}function qo(e,n){if(1&e){const e=t.ɵɵgetCurrentView();t.ɵɵelementStart(0,"tb-file-input",20),t.ɵɵpipe(1,"translate"),t.ɵɵpipe(2,"translate"),t.ɵɵlistener("fileNameChanged",(function(n){t.ɵɵrestoreView(e);const r=t.ɵɵnextContext();return t.ɵɵresetView(r.azureIotHubConfigForm.get("credentials.caCertFileName").setValue(n))})),t.ɵɵelementEnd(),t.ɵɵelementStart(3,"tb-file-input",21),t.ɵɵpipe(4,"translate"),t.ɵɵpipe(5,"translate"),t.ɵɵlistener("fileNameChanged",(function(n){t.ɵɵrestoreView(e);const r=t.ɵɵnextContext();return t.ɵɵresetView(r.azureIotHubConfigForm.get("credentials.certFileName").setValue(n))})),t.ɵɵelementEnd(),t.ɵɵelementStart(6,"tb-file-input",22),t.ɵɵpipe(7,"translate"),t.ɵɵpipe(8,"translate"),t.ɵɵlistener("fileNameChanged",(function(n){t.ɵɵrestoreView(e);const r=t.ɵɵnextContext();return t.ɵɵresetView(r.azureIotHubConfigForm.get("credentials.privateKeyFileName").setValue(n))})),t.ɵɵelementEnd(),t.ɵɵelementStart(9,"mat-form-field",5)(10,"mat-label",2),t.ɵɵtext(11,"tb.rulenode.private-key-password"),t.ɵɵelementEnd(),t.ɵɵelement(12,"input",23)(13,"tb-toggle-password",19),t.ɵɵelementEnd()}if(2&e){const e=t.ɵɵnextContext();t.ɵɵpropertyInterpolate("label",t.ɵɵpipeBind1(1,9,"tb.rulenode.azure-ca-cert")),t.ɵɵpropertyInterpolate("dropLabel",t.ɵɵpipeBind1(2,11,"tb.rulenode.drop-file")),t.ɵɵproperty("existingFileName",e.azureIotHubConfigForm.get("credentials.caCertFileName").value),t.ɵɵadvance(3),t.ɵɵpropertyInterpolate("label",t.ɵɵpipeBind1(4,13,"tb.rulenode.cert")),t.ɵɵpropertyInterpolate("dropLabel",t.ɵɵpipeBind1(5,15,"tb.rulenode.drop-file")),t.ɵɵproperty("existingFileName",e.azureIotHubConfigForm.get("credentials.certFileName").value),t.ɵɵadvance(3),t.ɵɵpropertyInterpolate("label",t.ɵɵpipeBind1(7,17,"tb.rulenode.private-key")),t.ɵɵpropertyInterpolate("dropLabel",t.ɵɵpipeBind1(8,19,"tb.rulenode.drop-file")),t.ɵɵproperty("existingFileName",e.azureIotHubConfigForm.get("credentials.privateKeyFileName").value)}}e("RulenodeCoreConfigEnrichmentModule",bo),("undefined"==typeof ngJitMode||ngJitMode)&&t.ɵɵsetNgModuleScope(bo,{declarations:[Vi,Li,Oi,mo,uo,po,fo,ho,Mi,yo],imports:[$,S,xi],exports:[Vi,Li,Oi,mo,uo,po,fo,ho,Mi,yo]});class Ao extends i{constructor(e,t){super(e),this.store=e,this.fb=t,this.allAzureIotHubCredentialsTypes=Vt,this.azureIotHubCredentialsTypeTranslationsMap=Ot}configForm(){return this.azureIotHubConfigForm}onConfigurationSet(e){this.azureIotHubConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[N.required]],host:[e?e.host:null,[N.required]],port:[e?e.port:null,[N.required,N.min(1),N.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[N.required,N.min(1),N.max(200)]],clientId:[e?e.clientId:null,[N.required]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[N.required]],sasKey:[e&&e.credentials?e.credentials.sasKey:null,[]],caCert:[e&&e.credentials?e.credentials.caCert:null,[]],caCertFileName:[e&&e.credentials?e.credentials.caCertFileName:null,[]],privateKey:[e&&e.credentials?e.credentials.privateKey:null,[]],privateKeyFileName:[e&&e.credentials?e.credentials.privateKeyFileName:null,[]],cert:[e&&e.credentials?e.credentials.cert:null,[]],certFileName:[e&&e.credentials?e.credentials.certFileName:null,[]],password:[e&&e.credentials?e.credentials.password:null,[]]})})}prepareOutputConfig(e){const t=e.credentials.type;return"sas"===t&&(e.credentials={type:t,sasKey:e.credentials.sasKey,caCert:e.credentials.caCert,caCertFileName:e.credentials.caCertFileName}),e}validatorTriggers(){return["credentials.type"]}updateValidators(e){const t=this.azureIotHubConfigForm.get("credentials"),n=t.get("type").value;switch(e&&t.reset({type:n},{emitEvent:!1}),t.get("sasKey").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),n){case"sas":t.get("sasKey").setValidators([N.required]);break;case"cert.PEM":t.get("privateKey").setValidators([N.required]),t.get("privateKeyFileName").setValidators([N.required]),t.get("cert").setValidators([N.required]),t.get("certFileName").setValidators([N.required])}t.get("sasKey").updateValueAndValidity({emitEvent:e}),t.get("privateKey").updateValueAndValidity({emitEvent:e}),t.get("privateKeyFileName").updateValueAndValidity({emitEvent:e}),t.get("cert").updateValueAndValidity({emitEvent:e}),t.get("certFileName").updateValueAndValidity({emitEvent:e})}static{this.ɵfac=function(e){return new(e||Ao)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Ao,selectors:[["tb-external-node-azure-iot-hub-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:37,vars:10,consts:[[1,"flex","flex-col",3,"formGroup"],["subscriptSizing","dynamic",1,"mat-block"],["translate",""],["required","","matInput","","formControlName","topicPattern"],[4,"ngIf"],[1,"mat-block"],["required","","matInput","","formControlName","host"],["required","","matInput","","formControlName","clientId","autocomplete","new-clientId"],[1,"tb-mqtt-credentials-panel-group"],["translate","",1,"tb-required"],["formGroupName","credentials",1,"flex","flex-col"],["formControlName","type","required",""],[3,"value",4,"ngFor","ngForOf"],[1,"flex","flex-col",3,"ngSwitch"],["ngSwitchCase","anonymous"],["ngSwitchCase","sas"],["ngSwitchCase","cert.PEM"],[3,"value"],["type","password","required","","matInput","","formControlName","sasKey","autocomplete","new-password"],["matSuffix",""],["formControlName","caCert","inputId","caCertSelect","noFileText","tb.rulenode.no-file",3,"fileNameChanged","existingFileName","label","dropLabel"],["formControlName","cert","inputId","CertSelect","required","","requiredAsError","","noFileText","tb.rulenode.no-file",3,"fileNameChanged","existingFileName","label","dropLabel"],["formControlName","privateKey","inputId","privateKeySelect","required","","requiredAsError","","noFileText","tb.rulenode.no-file",2,"padding-bottom","8px",3,"fileNameChanged","existingFileName","label","dropLabel"],["type","password","matInput","","formControlName","password","autocomplete","new-password"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"mat-form-field",1)(2,"mat-label",2),t.ɵɵtext(3,"tb.rulenode.topic"),t.ɵɵelementEnd(),t.ɵɵelement(4,"input",3),t.ɵɵtemplate(5,vo,3,3,"mat-error",4),t.ɵɵelementStart(6,"mat-hint",2),t.ɵɵtext(7,"tb.rulenode.general-pattern-hint"),t.ɵɵelementEnd()(),t.ɵɵelementStart(8,"mat-form-field",5)(9,"mat-label",2),t.ɵɵtext(10,"tb.rulenode.hostname"),t.ɵɵelementEnd(),t.ɵɵelement(11,"input",6),t.ɵɵtemplate(12,xo,3,3,"mat-error",4),t.ɵɵelementEnd(),t.ɵɵelementStart(13,"mat-form-field",5)(14,"mat-label",2),t.ɵɵtext(15,"tb.rulenode.device-id"),t.ɵɵelementEnd(),t.ɵɵelement(16,"input",7),t.ɵɵtemplate(17,Co,3,3,"mat-error",4),t.ɵɵelementEnd(),t.ɵɵelementStart(18,"mat-accordion")(19,"mat-expansion-panel",8)(20,"mat-expansion-panel-header")(21,"mat-panel-title",9),t.ɵɵtext(22,"tb.rulenode.credentials"),t.ɵɵelementEnd(),t.ɵɵelementStart(23,"mat-panel-description"),t.ɵɵtext(24),t.ɵɵpipe(25,"translate"),t.ɵɵelementEnd()(),t.ɵɵelementStart(26,"section",10)(27,"mat-form-field",5)(28,"mat-label",2),t.ɵɵtext(29,"tb.rulenode.credentials-type"),t.ɵɵelementEnd(),t.ɵɵelementStart(30,"mat-select",11),t.ɵɵtemplate(31,So,3,4,"mat-option",12),t.ɵɵelementEnd(),t.ɵɵtemplate(32,To,3,3,"mat-error",4),t.ɵɵelementEnd(),t.ɵɵelementStart(33,"section",13),t.ɵɵtemplate(34,Io,0,0,"ng-template",14)(35,Fo,9,8,"ng-template",15)(36,qo,14,21,"ng-template",16),t.ɵɵelementEnd()()()()()),2&e&&(t.ɵɵproperty("formGroup",n.azureIotHubConfigForm),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",n.azureIotHubConfigForm.get("topicPattern").hasError("required")),t.ɵɵadvance(7),t.ɵɵproperty("ngIf",n.azureIotHubConfigForm.get("host").hasError("required")),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",n.azureIotHubConfigForm.get("clientId").hasError("required")),t.ɵɵadvance(7),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(25,8,n.azureIotHubCredentialsTypeTranslationsMap.get(n.azureIotHubConfigForm.get("credentials.type").value))," "),t.ɵɵadvance(7),t.ɵɵproperty("ngForOf",n.allAzureIotHubCredentialsTypes),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.azureIotHubConfigForm.get("credentials.type").hasError("required")),t.ɵɵadvance(),t.ɵɵproperty("ngSwitch",n.azureIotHubConfigForm.get("credentials.type").value))},dependencies:t.ɵɵgetComponentDepsFactory(Ao),styles:["[_nghost-%COMP%] .tb-mqtt-credentials-panel-group[_ngcontent-%COMP%]{margin:0 6px}"]})}}function ko(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.topic-pattern-required")," "))}function No(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.bootstrap-servers-required")," "))}function wo(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.min-retries-message")," "))}function Mo(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.min-batch-size-bytes-message")," "))}function Bo(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.min-linger-ms-message")," "))}function Vo(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.min-buffer-memory-bytes-message")," "))}function Oo(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",21),t.ɵɵtext(1),t.ɵɵelementEnd()),2&e){const e=n.$implicit;t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",e," ")}}function Do(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.key-serializer-required")," "))}function Lo(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.value-serializer-required")," "))}function Po(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",21),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=n.$implicit,r=t.ɵɵnextContext(2);t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,2,r.ToByteStandartCharsetTypeTranslationMap.get(e))," ")}}function Ro(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-form-field",22)(1,"mat-label",2),t.ɵɵtext(2,"tb.rulenode.charset-encoding"),t.ɵɵelementEnd(),t.ɵɵelementStart(3,"mat-select",23),t.ɵɵtemplate(4,Po,3,4,"mat-option",14),t.ɵɵelementEnd()()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(4),t.ɵɵproperty("ngForOf",e.ToByteStandartCharsetTypesValues)}}e("AzureIotHubConfigComponent",Ao);class _o extends i{constructor(e,t){super(e),this.store=e,this.fb=t,this.ackValues=["all","-1","0","1"],this.ToByteStandartCharsetTypesValues=Lt,this.ToByteStandartCharsetTypeTranslationMap=Pt}configForm(){return this.kafkaConfigForm}onConfigurationSet(e){this.kafkaConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[N.required]],keyPattern:[e?e.keyPattern:null],bootstrapServers:[e?e.bootstrapServers:null,[N.required]],retries:[e?e.retries:null,[N.min(0)]],batchSize:[e?e.batchSize:null,[N.min(0)]],linger:[e?e.linger:null,[N.min(0)]],bufferMemory:[e?e.bufferMemory:null,[N.min(0)]],acks:[e?e.acks:null,[N.required]],keySerializer:[e?e.keySerializer:null,[N.required]],valueSerializer:[e?e.valueSerializer:null,[N.required]],otherProperties:[e?e.otherProperties:null,[]],addMetadataKeyValuesAsKafkaHeaders:[!!e&&e.addMetadataKeyValuesAsKafkaHeaders,[]],kafkaHeadersCharset:[e?e.kafkaHeadersCharset:null,[]]})}validatorTriggers(){return["addMetadataKeyValuesAsKafkaHeaders"]}updateValidators(e){this.kafkaConfigForm.get("addMetadataKeyValuesAsKafkaHeaders").value?this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([N.required]):this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([]),this.kafkaConfigForm.get("kafkaHeadersCharset").updateValueAndValidity({emitEvent:e})}static{this.ɵfac=function(e){return new(e||_o)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:_o,selectors:[["tb-external-node-kafka-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:65,vars:14,consts:[[1,"flex","flex-col",3,"formGroup"],["subscriptSizing","dynamic",1,"mat-block"],["translate",""],["required","","matInput","","formControlName","topicPattern"],[4,"ngIf"],["matInput","","formControlName","keyPattern"],["translate","",1,"tb-hint"],[1,"mat-block"],["required","","matInput","","formControlName","bootstrapServers"],["type","number","step","1","min","0","matInput","","formControlName","retries"],["type","number","step","1","min","0","matInput","","formControlName","batchSize"],["type","number","step","1","min","0","matInput","","formControlName","linger"],["type","number","step","1","min","0","matInput","","formControlName","bufferMemory"],["formControlName","acks","required",""],[3,"value",4,"ngFor","ngForOf"],["required","","matInput","","formControlName","keySerializer"],["required","","matInput","","formControlName","valueSerializer"],["translate","",1,"tb-title"],["required","false","formControlName","otherProperties","keyText","tb.rulenode.key","keyRequiredText","tb.rulenode.key-required","valText","tb.rulenode.value","valRequiredText","tb.rulenode.value-required"],["formControlName","addMetadataKeyValuesAsKafkaHeaders",1,"flex-1"],["class","mat-block flex-1",4,"ngIf"],[3,"value"],[1,"mat-block","flex-1"],["formControlName","kafkaHeadersCharset","required",""]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"mat-form-field",1)(2,"mat-label",2),t.ɵɵtext(3,"tb.rulenode.topic-pattern"),t.ɵɵelementEnd(),t.ɵɵelement(4,"input",3),t.ɵɵtemplate(5,ko,3,3,"mat-error",4),t.ɵɵelementStart(6,"mat-hint",2),t.ɵɵtext(7,"tb.rulenode.general-pattern-hint"),t.ɵɵelementEnd()(),t.ɵɵelementStart(8,"mat-form-field",1)(9,"mat-label",2),t.ɵɵtext(10,"tb.rulenode.key-pattern"),t.ɵɵelementEnd(),t.ɵɵelement(11,"input",5),t.ɵɵelementStart(12,"mat-hint",2),t.ɵɵtext(13,"tb.rulenode.general-pattern-hint"),t.ɵɵelementEnd()(),t.ɵɵelementStart(14,"div",6),t.ɵɵtext(15,"tb.rulenode.key-pattern-hint"),t.ɵɵelementEnd(),t.ɵɵelementStart(16,"mat-form-field",7)(17,"mat-label",2),t.ɵɵtext(18,"tb.rulenode.bootstrap-servers"),t.ɵɵelementEnd(),t.ɵɵelement(19,"input",8),t.ɵɵtemplate(20,No,3,3,"mat-error",4),t.ɵɵelementEnd(),t.ɵɵelementStart(21,"mat-form-field",7)(22,"mat-label",2),t.ɵɵtext(23,"tb.rulenode.retries"),t.ɵɵelementEnd(),t.ɵɵelement(24,"input",9),t.ɵɵtemplate(25,wo,3,3,"mat-error",4),t.ɵɵelementEnd(),t.ɵɵelementStart(26,"mat-form-field",7)(27,"mat-label",2),t.ɵɵtext(28,"tb.rulenode.batch-size-bytes"),t.ɵɵelementEnd(),t.ɵɵelement(29,"input",10),t.ɵɵtemplate(30,Mo,3,3,"mat-error",4),t.ɵɵelementEnd(),t.ɵɵelementStart(31,"mat-form-field",7)(32,"mat-label",2),t.ɵɵtext(33,"tb.rulenode.linger-ms"),t.ɵɵelementEnd(),t.ɵɵelement(34,"input",11),t.ɵɵtemplate(35,Bo,3,3,"mat-error",4),t.ɵɵelementEnd(),t.ɵɵelementStart(36,"mat-form-field",7)(37,"mat-label",2),t.ɵɵtext(38,"tb.rulenode.buffer-memory-bytes"),t.ɵɵelementEnd(),t.ɵɵelement(39,"input",12),t.ɵɵtemplate(40,Vo,3,3,"mat-error",4),t.ɵɵelementEnd(),t.ɵɵelementStart(41,"mat-form-field",7)(42,"mat-label",2),t.ɵɵtext(43,"tb.rulenode.acks"),t.ɵɵelementEnd(),t.ɵɵelementStart(44,"mat-select",13),t.ɵɵtemplate(45,Oo,2,2,"mat-option",14),t.ɵɵelementEnd()(),t.ɵɵelementStart(46,"mat-form-field",7)(47,"mat-label",2),t.ɵɵtext(48,"tb.rulenode.key-serializer"),t.ɵɵelementEnd(),t.ɵɵelement(49,"input",15),t.ɵɵtemplate(50,Do,3,3,"mat-error",4),t.ɵɵelementEnd(),t.ɵɵelementStart(51,"mat-form-field",7)(52,"mat-label",2),t.ɵɵtext(53,"tb.rulenode.value-serializer"),t.ɵɵelementEnd(),t.ɵɵelement(54,"input",16),t.ɵɵtemplate(55,Lo,3,3,"mat-error",4),t.ɵɵelementEnd(),t.ɵɵelementStart(56,"label",17),t.ɵɵtext(57,"tb.rulenode.other-properties"),t.ɵɵelementEnd(),t.ɵɵelement(58,"tb-kv-map-config-old",18),t.ɵɵelementStart(59,"mat-checkbox",19),t.ɵɵtext(60),t.ɵɵpipe(61,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(62,"div",6),t.ɵɵtext(63,"tb.rulenode.add-metadata-key-values-as-kafka-headers-hint"),t.ɵɵelementEnd(),t.ɵɵtemplate(64,Ro,5,1,"mat-form-field",20),t.ɵɵelementEnd()),2&e&&(t.ɵɵproperty("formGroup",n.kafkaConfigForm),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",n.kafkaConfigForm.get("topicPattern").hasError("required")),t.ɵɵadvance(15),t.ɵɵproperty("ngIf",n.kafkaConfigForm.get("bootstrapServers").hasError("required")),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",n.kafkaConfigForm.get("retries").hasError("min")),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",n.kafkaConfigForm.get("batchSize").hasError("min")),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",n.kafkaConfigForm.get("linger").hasError("min")),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",n.kafkaConfigForm.get("bufferMemory").hasError("min")),t.ɵɵadvance(5),t.ɵɵproperty("ngForOf",n.ackValues),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",n.kafkaConfigForm.get("keySerializer").hasError("required")),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",n.kafkaConfigForm.get("valueSerializer").hasError("required")),t.ɵɵadvance(5),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(61,12,"tb.rulenode.add-metadata-key-values-as-kafka-headers")," "),t.ɵɵadvance(4),t.ɵɵproperty("ngIf",n.kafkaConfigForm.get("addMetadataKeyValuesAsKafkaHeaders").value))},dependencies:t.ɵɵgetComponentDepsFactory(_o),encapsulation:2})}}function jo(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.topic-pattern-required")," "))}function Go(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.host-required")," "))}function Ko(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.port-required")," "))}function Uo(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.port-range")," "))}function Ho(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.port-range")," "))}function zo(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.connect-timeout-required")," "))}function $o(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.connect-timeout-range")," "))}function Qo(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.connect-timeout-range")," "))}e("KafkaConfigComponent",_o);class Jo extends i{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.mqttConfigForm}onConfigurationSet(e){this.mqttConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[N.required]],host:[e?e.host:null,[N.required]],port:[e?e.port:null,[N.required,N.min(1),N.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[N.required,N.min(1),N.max(200)]],clientId:[e?e.clientId:null,[]],appendClientIdSuffix:[{value:!!e&&e.appendClientIdSuffix,disabled:!(e&&G(e.clientId))},[]],parseToPlainText:[!!e&&e.parseToPlainText,[]],cleanSession:[!!e&&e.cleanSession,[]],retainedMessage:[!!e&&e.retainedMessage,[]],ssl:[!!e&&e.ssl,[]],credentials:[e?e.credentials:null,[]]})}updateValidators(e){G(this.mqttConfigForm.get("clientId").value)?this.mqttConfigForm.get("appendClientIdSuffix").enable({emitEvent:!1}):this.mqttConfigForm.get("appendClientIdSuffix").disable({emitEvent:!1}),this.mqttConfigForm.get("appendClientIdSuffix").updateValueAndValidity({emitEvent:e})}validatorTriggers(){return["clientId"]}static{this.ɵfac=function(e){return new(e||Jo)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Jo,selectors:[["tb-external-node-mqtt-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:57,vars:34,consts:[[1,"flex","flex-col",3,"formGroup"],["subscriptSizing","dynamic",1,"mat-block"],["translate",""],["required","","matInput","","formControlName","topicPattern"],[4,"ngIf"],[1,"flex","flex-1","flex-col","gt-sm:flex-row","gt-sm:gap-2"],[1,"mat-block","gt-sm:max-w-60%","gt-sm:flex-full"],["required","","matInput","","formControlName","host"],[1,"mat-block","gt-sm:max-w-40%","gt-sm:flex-full"],["required","","type","number","step","1","min","1","max","65535","matInput","","formControlName","port"],["required","","type","number","step","1","min","1","max","200","matInput","","formControlName","connectTimeoutSec"],["matInput","","formControlName","clientId"],["formControlName","appendClientIdSuffix"],[1,"tb-hint"],["formControlName","parseToPlainText"],["formControlName","cleanSession"],["formControlName","retainedMessage"],["formControlName","ssl"],["formControlName","credentials",3,"passwordFieldRequired"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"mat-form-field",1)(2,"mat-label",2),t.ɵɵtext(3,"tb.rulenode.topic-pattern"),t.ɵɵelementEnd(),t.ɵɵelement(4,"input",3),t.ɵɵtemplate(5,jo,3,3,"mat-error",4),t.ɵɵelementStart(6,"mat-hint",2),t.ɵɵtext(7,"tb.rulenode.general-pattern-hint"),t.ɵɵelementEnd()(),t.ɵɵelementStart(8,"div",5)(9,"mat-form-field",6)(10,"mat-label",2),t.ɵɵtext(11,"tb.rulenode.host"),t.ɵɵelementEnd(),t.ɵɵelement(12,"input",7),t.ɵɵtemplate(13,Go,3,3,"mat-error",4),t.ɵɵelementEnd(),t.ɵɵelementStart(14,"mat-form-field",8)(15,"mat-label",2),t.ɵɵtext(16,"tb.rulenode.port"),t.ɵɵelementEnd(),t.ɵɵelement(17,"input",9),t.ɵɵtemplate(18,Ko,3,3,"mat-error",4)(19,Uo,3,3,"mat-error",4)(20,Ho,3,3,"mat-error",4),t.ɵɵelementEnd(),t.ɵɵelementStart(21,"mat-form-field",8)(22,"mat-label",2),t.ɵɵtext(23,"tb.rulenode.connect-timeout"),t.ɵɵelementEnd(),t.ɵɵelement(24,"input",10),t.ɵɵtemplate(25,zo,3,3,"mat-error",4)(26,$o,3,3,"mat-error",4)(27,Qo,3,3,"mat-error",4),t.ɵɵelementEnd()(),t.ɵɵelementStart(28,"mat-form-field",1)(29,"mat-label",2),t.ɵɵtext(30,"tb.rulenode.client-id"),t.ɵɵelementEnd(),t.ɵɵelement(31,"input",11),t.ɵɵelementStart(32,"mat-hint"),t.ɵɵtext(33),t.ɵɵpipe(34,"translate"),t.ɵɵelementEnd()(),t.ɵɵelementStart(35,"mat-checkbox",12),t.ɵɵtext(36),t.ɵɵpipe(37,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(38,"div",13),t.ɵɵtext(39),t.ɵɵpipe(40,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(41,"mat-checkbox",14),t.ɵɵtext(42),t.ɵɵpipe(43,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(44,"div",13),t.ɵɵtext(45),t.ɵɵpipe(46,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(47,"mat-checkbox",15),t.ɵɵtext(48),t.ɵɵpipe(49,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(50,"mat-checkbox",16),t.ɵɵtext(51),t.ɵɵpipe(52,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(53,"mat-checkbox",17),t.ɵɵtext(54),t.ɵɵpipe(55,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(56,"tb-credentials-config",18),t.ɵɵelementEnd()),2&e&&(t.ɵɵproperty("formGroup",n.mqttConfigForm),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",n.mqttConfigForm.get("topicPattern").hasError("required")),t.ɵɵadvance(8),t.ɵɵproperty("ngIf",n.mqttConfigForm.get("host").hasError("required")),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",n.mqttConfigForm.get("port").hasError("required")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.mqttConfigForm.get("port").hasError("min")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.mqttConfigForm.get("port").hasError("max")),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",n.mqttConfigForm.get("connectTimeoutSec").hasError("required")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.mqttConfigForm.get("connectTimeoutSec").hasError("min")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.mqttConfigForm.get("connectTimeoutSec").hasError("max")),t.ɵɵadvance(6),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(34,18,"tb.rulenode.client-id-hint")),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(37,20,"tb.rulenode.append-client-id-suffix")," "),t.ɵɵadvance(3),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(40,22,"tb.rulenode.client-id-suffix-hint")),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(43,24,"tb.rulenode.parse-to-plain-text")," "),t.ɵɵadvance(3),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(46,26,"tb.rulenode.parse-to-plain-text-hint")),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(49,28,"tb.rulenode.clean-session")," "),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(52,30,"tb.rulenode.retained-message")," "),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(55,32,"tb.rulenode.enable-ssl")," "),t.ɵɵadvance(2),t.ɵɵproperty("passwordFieldRequired",!1))},dependencies:t.ɵɵgetComponentDepsFactory(Jo),styles:["[_nghost-%COMP%] .tb-mqtt-credentials-panel-group[_ngcontent-%COMP%]{margin:0 6px}"]})}}e("MqttConfigComponent",Jo);class Yo extends i{constructor(e,t){super(e),this.store=e,this.fb=t,this.notificationType=E,this.entityType=u}configForm(){return this.notificationConfigForm}onConfigurationSet(e){this.notificationConfigForm=this.fb.group({templateId:[e?e.templateId:null,[N.required]],targets:[e?e.targets:[],[N.required]]})}static{this.ɵfac=function(e){return new(e||Yo)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.FormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Yo,selectors:[["tb-external-node-notification-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:6,vars:13,consts:[[1,"flex","flex-col",3,"formGroup"],["required","","allowCreate","","formControlName","templateId",3,"notificationTypes"],["required","","formControlName","targets",3,"labelText","placeholderText","requiredText","entityType","subType"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0),t.ɵɵelement(1,"tb-template-autocomplete",1)(2,"tb-entity-list",2),t.ɵɵpipe(3,"translate"),t.ɵɵpipe(4,"translate"),t.ɵɵpipe(5,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵproperty("formGroup",n.notificationConfigForm),t.ɵɵadvance(),t.ɵɵproperty("notificationTypes",n.notificationType.RULE_NODE),t.ɵɵadvance(),t.ɵɵpropertyInterpolate("labelText",t.ɵɵpipeBind1(3,7,"notification.recipients")),t.ɵɵpropertyInterpolate("placeholderText",t.ɵɵpipeBind1(4,9,"notification.recipient")),t.ɵɵpropertyInterpolate("requiredText",t.ɵɵpipeBind1(5,11,"notification.recipients-required")),t.ɵɵpropertyInterpolate("entityType",n.entityType.NOTIFICATION_TARGET),t.ɵɵpropertyInterpolate("subType",n.notificationType.RULE_NODE))},dependencies:t.ɵɵgetComponentDepsFactory(Yo),encapsulation:2})}}function Wo(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.gcp-project-id-required")," "))}function Xo(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.pubsub-topic-name-required")," "))}e("NotificationConfigComponent",Yo);class Zo extends i{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.pubSubConfigForm}onConfigurationSet(e){this.pubSubConfigForm=this.fb.group({projectId:[e?e.projectId:null,[N.required]],topicName:[e?e.topicName:null,[N.required]],serviceAccountKey:[e?e.serviceAccountKey:null,[N.required]],serviceAccountKeyFileName:[e?e.serviceAccountKeyFileName:null,[N.required]],messageAttributes:[e?e.messageAttributes:null,[]]})}static{this.ɵfac=function(e){return new(e||Zo)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Zo,selectors:[["tb-external-node-pub-sub-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:20,vars:16,consts:[[1,"flex","flex-col",3,"formGroup"],[1,"mat-block"],["translate",""],["required","","matInput","","formControlName","projectId"],[4,"ngIf"],["required","","matInput","","formControlName","topicName"],["formControlName","serviceAccountKey","required","","requiredAsError","","noFileText","tb.rulenode.no-file",2,"padding-bottom","24px",3,"fileNameChanged","existingFileName","label","dropLabel"],["translate","",1,"tb-title"],[1,"tb-hint",3,"innerHTML"],["required","false","formControlName","messageAttributes","keyText","tb.rulenode.name","keyRequiredText","tb.rulenode.name-required","valText","tb.rulenode.value","valRequiredText","tb.rulenode.value-required"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"mat-form-field",1)(2,"mat-label",2),t.ɵɵtext(3,"tb.rulenode.gcp-project-id"),t.ɵɵelementEnd(),t.ɵɵelement(4,"input",3),t.ɵɵtemplate(5,Wo,3,3,"mat-error",4),t.ɵɵelementEnd(),t.ɵɵelementStart(6,"mat-form-field",1)(7,"mat-label",2),t.ɵɵtext(8,"tb.rulenode.pubsub-topic-name"),t.ɵɵelementEnd(),t.ɵɵelement(9,"input",5),t.ɵɵtemplate(10,Xo,3,3,"mat-error",4),t.ɵɵelementEnd(),t.ɵɵelementStart(11,"tb-file-input",6),t.ɵɵpipe(12,"translate"),t.ɵɵpipe(13,"translate"),t.ɵɵlistener("fileNameChanged",(function(e){return n.pubSubConfigForm.get("serviceAccountKeyFileName").setValue(e)})),t.ɵɵelementEnd(),t.ɵɵelementStart(14,"label",7),t.ɵɵtext(15,"tb.rulenode.message-attributes"),t.ɵɵelementEnd(),t.ɵɵelement(16,"div",8),t.ɵɵpipe(17,"translate"),t.ɵɵpipe(18,"safe"),t.ɵɵelement(19,"tb-kv-map-config-old",9),t.ɵɵelementEnd()),2&e&&(t.ɵɵproperty("formGroup",n.pubSubConfigForm),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",n.pubSubConfigForm.get("projectId").hasError("required")),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",n.pubSubConfigForm.get("topicName").hasError("required")),t.ɵɵadvance(),t.ɵɵpropertyInterpolate("label",t.ɵɵpipeBind1(12,7,"tb.rulenode.gcp-service-account-key")),t.ɵɵpropertyInterpolate("dropLabel",t.ɵɵpipeBind1(13,9,"tb.rulenode.drop-file")),t.ɵɵproperty("existingFileName",n.pubSubConfigForm.get("serviceAccountKeyFileName").value),t.ɵɵadvance(5),t.ɵɵproperty("innerHTML",t.ɵɵpipeBind2(18,13,t.ɵɵpipeBind1(17,11,"tb.rulenode.message-attributes-hint"),"html"),t.ɵɵsanitizeHtml))},dependencies:t.ɵɵgetComponentDepsFactory(Zo),encapsulation:2})}}function el(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",22),t.ɵɵtext(1),t.ɵɵelementEnd()),2&e){const e=n.$implicit;t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",e," ")}}function tl(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.host-required")," "))}function nl(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.port-required")," "))}function rl(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.port-range")," "))}function al(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.port-range")," "))}function il(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.min-connection-timeout-ms-message")," "))}function ol(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.min-handshake-timeout-ms-message")," "))}e("PubSubConfigComponent",Zo);class ll extends i{constructor(e,t){super(e),this.store=e,this.fb=t,this.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"]}configForm(){return this.rabbitMqConfigForm}onConfigurationSet(e){this.rabbitMqConfigForm=this.fb.group({exchangeNamePattern:[e?e.exchangeNamePattern:null,[]],routingKeyPattern:[e?e.routingKeyPattern:null,[]],messageProperties:[e?e.messageProperties:null,[]],host:[e?e.host:null,[N.required]],port:[e?e.port:null,[N.required,N.min(1),N.max(65535)]],virtualHost:[e?e.virtualHost:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]],automaticRecoveryEnabled:[!!e&&e.automaticRecoveryEnabled,[]],connectionTimeout:[e?e.connectionTimeout:null,[N.min(0)]],handshakeTimeout:[e?e.handshakeTimeout:null,[N.min(0)]],clientProperties:[e?e.clientProperties:null,[]]})}static{this.ɵfac=function(e){return new(e||ll)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:ll,selectors:[["tb-external-node-rabbit-mq-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:56,vars:11,consts:[[1,"flex","flex-col",3,"formGroup"],[1,"mat-block"],["translate",""],["matInput","","formControlName","exchangeNamePattern"],["matInput","","formControlName","routingKeyPattern"],["formControlName","messageProperties"],[3,"value",4,"ngFor","ngForOf"],[1,"gt-sm:flex","gt-sm:flex-row","gt-sm:gap-2"],[1,"mat-block","gt-sm:max-w-60%","gt-sm:flex-full"],["required","","matInput","","formControlName","host"],[4,"ngIf"],[1,"mat-block","gt-sm:max-w-40%","gt-sm:flex-full"],["required","","type","number","step","1","min","1","max","65535","matInput","","formControlName","port"],["matInput","","formControlName","virtualHost"],["matInput","","formControlName","username"],["type","password","matInput","","formControlName","password"],["matSuffix",""],["formControlName","automaticRecoveryEnabled"],["type","number","step","1","min","0","matInput","","formControlName","connectionTimeout"],["type","number","step","1","min","0","matInput","","formControlName","handshakeTimeout"],["translate","",1,"tb-title"],["required","false","formControlName","clientProperties","keyText","tb.rulenode.key","keyRequiredText","tb.rulenode.key-required","valText","tb.rulenode.value","valRequiredText","tb.rulenode.value-required"],[3,"value"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"mat-form-field",1)(2,"mat-label",2),t.ɵɵtext(3,"tb.rulenode.exchange-name-pattern"),t.ɵɵelementEnd(),t.ɵɵelement(4,"input",3),t.ɵɵelementEnd(),t.ɵɵelementStart(5,"mat-form-field",1)(6,"mat-label",2),t.ɵɵtext(7,"tb.rulenode.routing-key-pattern"),t.ɵɵelementEnd(),t.ɵɵelement(8,"input",4),t.ɵɵelementEnd(),t.ɵɵelementStart(9,"mat-form-field",1)(10,"mat-label",2),t.ɵɵtext(11,"tb.rulenode.message-properties"),t.ɵɵelementEnd(),t.ɵɵelementStart(12,"mat-select",5),t.ɵɵtemplate(13,el,2,2,"mat-option",6),t.ɵɵelementEnd()(),t.ɵɵelementStart(14,"div",7)(15,"mat-form-field",8)(16,"mat-label",2),t.ɵɵtext(17,"tb.rulenode.host"),t.ɵɵelementEnd(),t.ɵɵelement(18,"input",9),t.ɵɵtemplate(19,tl,3,3,"mat-error",10),t.ɵɵelementEnd(),t.ɵɵelementStart(20,"mat-form-field",11)(21,"mat-label",2),t.ɵɵtext(22,"tb.rulenode.port"),t.ɵɵelementEnd(),t.ɵɵelement(23,"input",12),t.ɵɵtemplate(24,nl,3,3,"mat-error",10)(25,rl,3,3,"mat-error",10)(26,al,3,3,"mat-error",10),t.ɵɵelementEnd()(),t.ɵɵelementStart(27,"mat-form-field",1)(28,"mat-label",2),t.ɵɵtext(29,"tb.rulenode.virtual-host"),t.ɵɵelementEnd(),t.ɵɵelement(30,"input",13),t.ɵɵelementEnd(),t.ɵɵelementStart(31,"mat-form-field",1)(32,"mat-label",2),t.ɵɵtext(33,"tb.rulenode.username"),t.ɵɵelementEnd(),t.ɵɵelement(34,"input",14),t.ɵɵelementEnd(),t.ɵɵelementStart(35,"mat-form-field",1)(36,"mat-label",2),t.ɵɵtext(37,"tb.rulenode.password"),t.ɵɵelementEnd(),t.ɵɵelement(38,"input",15)(39,"tb-toggle-password",16),t.ɵɵelementEnd(),t.ɵɵelementStart(40,"mat-checkbox",17),t.ɵɵtext(41),t.ɵɵpipe(42,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(43,"mat-form-field",1)(44,"mat-label",2),t.ɵɵtext(45,"tb.rulenode.connection-timeout-ms"),t.ɵɵelementEnd(),t.ɵɵelement(46,"input",18),t.ɵɵtemplate(47,il,3,3,"mat-error",10),t.ɵɵelementEnd(),t.ɵɵelementStart(48,"mat-form-field",1)(49,"mat-label",2),t.ɵɵtext(50,"tb.rulenode.handshake-timeout-ms"),t.ɵɵelementEnd(),t.ɵɵelement(51,"input",19),t.ɵɵtemplate(52,ol,3,3,"mat-error",10),t.ɵɵelementEnd(),t.ɵɵelementStart(53,"label",20),t.ɵɵtext(54,"tb.rulenode.client-properties"),t.ɵɵelementEnd(),t.ɵɵelement(55,"tb-kv-map-config-old",21),t.ɵɵelementEnd()),2&e&&(t.ɵɵproperty("formGroup",n.rabbitMqConfigForm),t.ɵɵadvance(13),t.ɵɵproperty("ngForOf",n.messageProperties),t.ɵɵadvance(6),t.ɵɵproperty("ngIf",n.rabbitMqConfigForm.get("host").hasError("required")),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",n.rabbitMqConfigForm.get("port").hasError("required")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.rabbitMqConfigForm.get("port").hasError("min")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.rabbitMqConfigForm.get("port").hasError("max")),t.ɵɵadvance(15),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(42,9,"tb.rulenode.automatic-recovery")," "),t.ɵɵadvance(6),t.ɵɵproperty("ngIf",n.rabbitMqConfigForm.get("connectionTimeout").hasError("min")),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",n.rabbitMqConfigForm.get("handshakeTimeout").hasError("min")))},dependencies:t.ɵɵgetComponentDepsFactory(ll),encapsulation:2})}}e("RabbitMqConfigComponent",ll);const sl=e=>({max:e});function pl(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.endpoint-url-pattern-required")," "))}function ml(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",20),t.ɵɵtext(1),t.ɵɵelementEnd()),2&e){const e=n.$implicit;t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",e," ")}}function dl(e,n){1&e&&(t.ɵɵelementStart(0,"mat-checkbox",21),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.use-simple-client-http-factory")," "))}function ul(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",20),t.ɵɵtext(1),t.ɵɵelementEnd()),2&e){const e=n.$implicit;t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",e," ")}}function cl(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.proxy-host-required")," "))}function fl(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.proxy-port-required")," "))}function gl(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.proxy-port-range")," "))}function hl(e,n){if(1&e&&(t.ɵɵelementStart(0,"div")(1,"div",23)(2,"mat-form-field",24)(3,"mat-label",2),t.ɵɵtext(4,"tb.rulenode.proxy-scheme"),t.ɵɵelementEnd(),t.ɵɵelementStart(5,"mat-select",25),t.ɵɵtemplate(6,ul,2,2,"mat-option",7),t.ɵɵelementEnd()(),t.ɵɵelementStart(7,"mat-form-field",26)(8,"mat-label",2),t.ɵɵtext(9,"tb.rulenode.proxy-host"),t.ɵɵelementEnd(),t.ɵɵelement(10,"input",27),t.ɵɵtemplate(11,cl,3,3,"mat-error",4),t.ɵɵelementEnd(),t.ɵɵelementStart(12,"mat-form-field",28)(13,"mat-label",2),t.ɵɵtext(14,"tb.rulenode.proxy-port"),t.ɵɵelementEnd(),t.ɵɵelement(15,"input",29),t.ɵɵtemplate(16,fl,3,3,"mat-error",4)(17,gl,3,3,"mat-error",4),t.ɵɵelementEnd()(),t.ɵɵelementStart(18,"mat-form-field",5)(19,"mat-label",2),t.ɵɵtext(20,"tb.rulenode.proxy-user"),t.ɵɵelementEnd(),t.ɵɵelement(21,"input",30),t.ɵɵelementEnd(),t.ɵɵelementStart(22,"mat-form-field",5)(23,"mat-label",2),t.ɵɵtext(24,"tb.rulenode.proxy-password"),t.ɵɵelementEnd(),t.ɵɵelement(25,"input",31),t.ɵɵelementEnd()()),2&e){const e=t.ɵɵnextContext(2);t.ɵɵadvance(6),t.ɵɵproperty("ngForOf",e.proxySchemes),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",e.restApiCallConfigForm.get("proxyHost").hasError("required")),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",e.restApiCallConfigForm.get("proxyPort").hasError("required")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",e.restApiCallConfigForm.get("proxyPort").hasError("min")||e.restApiCallConfigForm.get("proxyPort").hasError("max"))}}function yl(e,n){if(1&e&&(t.ɵɵelementStart(0,"div")(1,"mat-checkbox",22),t.ɵɵtext(2),t.ɵɵpipe(3,"translate"),t.ɵɵelementEnd(),t.ɵɵtemplate(4,hl,26,4,"div",4),t.ɵɵelementEnd()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(2),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(3,2,"tb.rulenode.use-system-proxy-properties")," "),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",!e.restApiCallConfigForm.get("useSystemProxyProperties").value)}}function bl(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.int-range")," "))}function vl(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-form-field",1)(1,"mat-label",2),t.ɵɵtext(2,"tb.rulenode.read-timeout"),t.ɵɵelementEnd(),t.ɵɵelement(3,"input",32),t.ɵɵelementStart(4,"mat-hint",2),t.ɵɵtext(5,"tb.rulenode.read-timeout-hint"),t.ɵɵelementEnd(),t.ɵɵtemplate(6,bl,3,3,"mat-error",4),t.ɵɵelementEnd()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(3),t.ɵɵproperty("max",e.IntLimit),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",e.restApiCallConfigForm.get("readTimeoutMs").hasError("max"))}}function xl(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.int-range")," "))}function Cl(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind2(2,1,"tb.rulenode.memory-buffer-size-range",t.ɵɵpureFunction1(4,sl,e.MemoryBufferSizeInKbLimit))," ")}}class Sl extends i{constructor(e,t){super(e),this.store=e,this.fb=t,this.proxySchemes=["http","https"],this.httpRequestTypes=Object.keys(Dt),this.MemoryBufferSizeInKbLimit=25e3,this.IntLimit=tn}configForm(){return this.restApiCallConfigForm}onConfigurationSet(e){this.restApiCallConfigForm=this.fb.group({restEndpointUrlPattern:[e?e.restEndpointUrlPattern:null,[N.required]],requestMethod:[e?e.requestMethod:null,[N.required]],useSimpleClientHttpFactory:[!!e&&e.useSimpleClientHttpFactory,[]],parseToPlainText:[!!e&&e.parseToPlainText,[]],ignoreRequestBody:[!!e&&e.ignoreRequestBody,[]],enableProxy:[!!e&&e.enableProxy,[]],useSystemProxyProperties:[!!e&&e.enableProxy,[]],proxyScheme:[e?e.proxyHost:null,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],readTimeoutMs:[e?e.readTimeoutMs:null,[N.min(0),N.max(tn)]],maxParallelRequestsCount:[e?e.maxParallelRequestsCount:null,[N.min(0),N.max(tn)]],headers:[e?e.headers:null,[]],credentials:[e?e.credentials:null,[]],maxInMemoryBufferSizeInKb:[e?e.maxInMemoryBufferSizeInKb:null,[N.min(1),N.max(this.MemoryBufferSizeInKbLimit)]]})}validatorTriggers(){return["useSimpleClientHttpFactory","enableProxy","useSystemProxyProperties"]}updateValidators(e){const t=this.restApiCallConfigForm.get("useSimpleClientHttpFactory").value,n=this.restApiCallConfigForm.get("enableProxy").value,r=this.restApiCallConfigForm.get("useSystemProxyProperties").value;n&&!r?(this.restApiCallConfigForm.get("proxyHost").setValidators(n?[N.required]:[]),this.restApiCallConfigForm.get("proxyPort").setValidators(n?[N.required,N.min(1),N.max(65535)]:[])):(this.restApiCallConfigForm.get("proxyHost").setValidators([]),this.restApiCallConfigForm.get("proxyPort").setValidators([]),t?this.restApiCallConfigForm.get("readTimeoutMs").setValidators([]):this.restApiCallConfigForm.get("readTimeoutMs").setValidators([N.min(0),N.max(tn)])),this.restApiCallConfigForm.get("readTimeoutMs").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("credentials").updateValueAndValidity({emitEvent:e})}static{this.ɵfac=function(e){return new(e||Sl)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Sl,selectors:[["tb-external-node-rest-api-call-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:48,vars:26,consts:[[1,"flex","flex-col",3,"formGroup"],["subscriptSizing","dynamic",1,"mat-block"],["translate",""],["required","","matInput","","formControlName","restEndpointUrlPattern"],[4,"ngIf"],[1,"mat-block"],["formControlName","requestMethod"],[3,"value",4,"ngFor","ngForOf"],["formControlName","enableProxy"],["formControlName","useSimpleClientHttpFactory",4,"ngIf"],["formControlName","parseToPlainText"],["translate","",1,"tb-hint",2,"padding-bottom","5px"],["formControlName","ignoreRequestBody"],["class","mat-block","subscriptSizing","dynamic",4,"ngIf"],["type","text","min","0","inputmode","numeric","pattern","[0-9]*","matInput","","formControlName","maxParallelRequestsCount",3,"max"],["type","text","min","1","inputmode","numeric","pattern","[0-9]*","matInput","","formControlName","maxInMemoryBufferSizeInKb",3,"max"],["translate","",1,"tb-title"],[1,"tb-hint",3,"innerHTML"],["required","false","formControlName","headers","keyText","tb.rulenode.header","keyRequiredText","tb.rulenode.header-required","valText","tb.rulenode.value","valRequiredText","tb.rulenode.value-required"],["formControlName","credentials",3,"disableCertPemCredentials"],[3,"value"],["formControlName","useSimpleClientHttpFactory"],["formControlName","useSystemProxyProperties"],[1,"gt-sm:flex","gt-sm:flex-row","gt-sm:gap-2"],[1,"mat-block","gt-sm:max-w-10%","gt-sm:flex-full"],["formControlName","proxyScheme"],[1,"md-block","gt-sm:max-w-50%","gt-sm:flex-full"],["matInput","","required","","formControlName","proxyHost"],[1,"mat-block","gt-sm:max-w-40%","gt-sm:flex-full"],["matInput","","required","","formControlName","proxyPort","type","number","step","1"],["matInput","","formControlName","proxyUser"],["matInput","","formControlName","proxyPassword"],["type","text","min","0","inputmode","numeric","pattern","[0-9]*","matInput","","formControlName","readTimeoutMs",3,"max"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"mat-form-field",1)(2,"mat-label",2),t.ɵɵtext(3,"tb.rulenode.endpoint-url-pattern"),t.ɵɵelementEnd(),t.ɵɵelement(4,"input",3),t.ɵɵtemplate(5,pl,3,3,"mat-error",4),t.ɵɵelementStart(6,"mat-hint",2),t.ɵɵtext(7,"tb.rulenode.general-pattern-hint"),t.ɵɵelementEnd()(),t.ɵɵelementStart(8,"mat-form-field",5)(9,"mat-label",2),t.ɵɵtext(10,"tb.rulenode.request-method"),t.ɵɵelementEnd(),t.ɵɵelementStart(11,"mat-select",6),t.ɵɵtemplate(12,ml,2,2,"mat-option",7),t.ɵɵelementEnd()(),t.ɵɵelementStart(13,"mat-checkbox",8),t.ɵɵtext(14),t.ɵɵpipe(15,"translate"),t.ɵɵelementEnd(),t.ɵɵtemplate(16,dl,3,3,"mat-checkbox",9),t.ɵɵelementStart(17,"mat-checkbox",10),t.ɵɵtext(18),t.ɵɵpipe(19,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(20,"div",11),t.ɵɵtext(21,"tb.rulenode.parse-to-plain-text-hint"),t.ɵɵelementEnd(),t.ɵɵelementStart(22,"mat-checkbox",12),t.ɵɵtext(23),t.ɵɵpipe(24,"translate"),t.ɵɵelementEnd(),t.ɵɵtemplate(25,yl,5,4,"div",4)(26,vl,7,2,"mat-form-field",13),t.ɵɵelementStart(27,"mat-form-field",1)(28,"mat-label",2),t.ɵɵtext(29,"tb.rulenode.max-parallel-requests-count"),t.ɵɵelementEnd(),t.ɵɵelement(30,"input",14),t.ɵɵelementStart(31,"mat-hint",2),t.ɵɵtext(32,"tb.rulenode.max-parallel-requests-count-hint"),t.ɵɵelementEnd(),t.ɵɵtemplate(33,xl,3,3,"mat-error",4),t.ɵɵelementEnd(),t.ɵɵelementStart(34,"mat-form-field",1)(35,"mat-label",2),t.ɵɵtext(36,"tb.rulenode.max-response-size"),t.ɵɵelementEnd(),t.ɵɵelement(37,"input",15),t.ɵɵelementStart(38,"mat-hint",2),t.ɵɵtext(39,"tb.rulenode.max-response-size-hint"),t.ɵɵelementEnd(),t.ɵɵtemplate(40,Cl,3,6,"mat-error",4),t.ɵɵelementEnd(),t.ɵɵelementStart(41,"label",16),t.ɵɵtext(42,"tb.rulenode.headers"),t.ɵɵelementEnd(),t.ɵɵelement(43,"div",17),t.ɵɵpipe(44,"translate"),t.ɵɵpipe(45,"safe"),t.ɵɵelement(46,"tb-kv-map-config-old",18)(47,"tb-credentials-config",19),t.ɵɵelementEnd()),2&e&&(t.ɵɵproperty("formGroup",n.restApiCallConfigForm),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",n.restApiCallConfigForm.get("restEndpointUrlPattern").hasError("required")),t.ɵɵadvance(7),t.ɵɵproperty("ngForOf",n.httpRequestTypes),t.ɵɵadvance(2),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(15,15,"tb.rulenode.enable-proxy")," "),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",!n.restApiCallConfigForm.get("enableProxy").value),t.ɵɵadvance(2),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(19,17,"tb.rulenode.parse-to-plain-text")," "),t.ɵɵadvance(5),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(24,19,"tb.rulenode.ignore-request-body")," "),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",n.restApiCallConfigForm.get("enableProxy").value),t.ɵɵadvance(),t.ɵɵproperty("ngIf",!n.restApiCallConfigForm.get("useSimpleClientHttpFactory").value||n.restApiCallConfigForm.get("enableProxy").value),t.ɵɵadvance(4),t.ɵɵproperty("max",n.IntLimit),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",n.restApiCallConfigForm.get("maxParallelRequestsCount").hasError("max")),t.ɵɵadvance(4),t.ɵɵproperty("max",n.MemoryBufferSizeInKbLimit),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",n.restApiCallConfigForm.get("maxInMemoryBufferSizeInKb").hasError("min")||n.restApiCallConfigForm.get("maxInMemoryBufferSizeInKb").hasError("max")),t.ɵɵadvance(3),t.ɵɵproperty("innerHTML",t.ɵɵpipeBind2(45,23,t.ɵɵpipeBind1(44,21,"tb.rulenode.headers-hint"),"html"),t.ɵɵsanitizeHtml),t.ɵɵadvance(4),t.ɵɵproperty("disableCertPemCredentials",n.restApiCallConfigForm.get("useSimpleClientHttpFactory").value))},dependencies:t.ɵɵgetComponentDepsFactory(Sl),encapsulation:2})}}function Tl(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",22),t.ɵɵtext(1),t.ɵɵelementEnd()),2&e){const e=n.$implicit;t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",e.toUpperCase()," ")}}function Il(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.smtp-host-required")," "))}function El(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.smtp-port-required")," "))}function Fl(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.smtp-port-range")," "))}function ql(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.smtp-port-range")," "))}function Al(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.timeout-required")," "))}function kl(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.min-timeout-msec-message")," "))}function Nl(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",22),t.ɵɵtext(1),t.ɵɵelementEnd()),2&e){const e=n.$implicit;t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",e," ")}}function wl(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-form-field",4)(1,"mat-label",5),t.ɵɵtext(2,"tb.rulenode.tls-version"),t.ɵɵelementEnd(),t.ɵɵelementStart(3,"mat-select",23),t.ɵɵtemplate(4,Nl,2,2,"mat-option",7),t.ɵɵelementEnd()()),2&e){const e=t.ɵɵnextContext(2);t.ɵɵadvance(4),t.ɵɵproperty("ngForOf",e.tlsVersions)}}function Ml(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.proxy-host-required")," "))}function Bl(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.proxy-port-required")," "))}function Vl(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.proxy-port-range")," "))}function Ol(e,n){if(1&e&&(t.ɵɵelementStart(0,"div")(1,"div",8)(2,"mat-form-field",9)(3,"mat-label",5),t.ɵɵtext(4,"tb.rulenode.proxy-host"),t.ɵɵelementEnd(),t.ɵɵelement(5,"input",24),t.ɵɵtemplate(6,Ml,3,3,"mat-error",11),t.ɵɵelementEnd(),t.ɵɵelementStart(7,"mat-form-field",12)(8,"mat-label",5),t.ɵɵtext(9,"tb.rulenode.proxy-port"),t.ɵɵelementEnd(),t.ɵɵelement(10,"input",25),t.ɵɵtemplate(11,Bl,3,3,"mat-error",11)(12,Vl,3,3,"mat-error",11),t.ɵɵelementEnd()(),t.ɵɵelementStart(13,"mat-form-field",4)(14,"mat-label",5),t.ɵɵtext(15,"tb.rulenode.proxy-user"),t.ɵɵelementEnd(),t.ɵɵelement(16,"input",26),t.ɵɵelementEnd(),t.ɵɵelementStart(17,"mat-form-field",4)(18,"mat-label",5),t.ɵɵtext(19,"tb.rulenode.proxy-password"),t.ɵɵelementEnd(),t.ɵɵelement(20,"input",27),t.ɵɵelementEnd()()),2&e){const e=t.ɵɵnextContext(2);t.ɵɵadvance(6),t.ɵɵproperty("ngIf",e.sendEmailConfigForm.get("proxyHost").hasError("required")),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",e.sendEmailConfigForm.get("proxyPort").hasError("required")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",e.sendEmailConfigForm.get("proxyPort").hasError("min")||e.sendEmailConfigForm.get("proxyPort").hasError("max"))}}function Dl(e,n){if(1&e&&(t.ɵɵelementStart(0,"section",3)(1,"mat-form-field",4)(2,"mat-label",5),t.ɵɵtext(3,"tb.rulenode.smtp-protocol"),t.ɵɵelementEnd(),t.ɵɵelementStart(4,"mat-select",6),t.ɵɵtemplate(5,Tl,2,2,"mat-option",7),t.ɵɵelementEnd()(),t.ɵɵelementStart(6,"div",8)(7,"mat-form-field",9)(8,"mat-label",5),t.ɵɵtext(9,"tb.rulenode.smtp-host"),t.ɵɵelementEnd(),t.ɵɵelement(10,"input",10),t.ɵɵtemplate(11,Il,3,3,"mat-error",11),t.ɵɵelementEnd(),t.ɵɵelementStart(12,"mat-form-field",12)(13,"mat-label",5),t.ɵɵtext(14,"tb.rulenode.smtp-port"),t.ɵɵelementEnd(),t.ɵɵelement(15,"input",13),t.ɵɵtemplate(16,El,3,3,"mat-error",11)(17,Fl,3,3,"mat-error",11)(18,ql,3,3,"mat-error",11),t.ɵɵelementEnd()(),t.ɵɵelementStart(19,"mat-form-field",4)(20,"mat-label",5),t.ɵɵtext(21,"tb.rulenode.timeout-msec"),t.ɵɵelementEnd(),t.ɵɵelement(22,"input",14),t.ɵɵtemplate(23,Al,3,3,"mat-error",11)(24,kl,3,3,"mat-error",11),t.ɵɵelementEnd(),t.ɵɵelementStart(25,"mat-checkbox",15),t.ɵɵtext(26),t.ɵɵpipe(27,"translate"),t.ɵɵelementEnd(),t.ɵɵtemplate(28,wl,5,1,"mat-form-field",16),t.ɵɵelementStart(29,"tb-checkbox",17),t.ɵɵtext(30),t.ɵɵpipe(31,"translate"),t.ɵɵelementEnd(),t.ɵɵtemplate(32,Ol,21,3,"div",11),t.ɵɵelementStart(33,"mat-form-field",18)(34,"mat-label",5),t.ɵɵtext(35,"tb.rulenode.username"),t.ɵɵelementEnd(),t.ɵɵelement(36,"input",19),t.ɵɵpipe(37,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(38,"mat-form-field",18)(39,"mat-label",5),t.ɵɵtext(40,"tb.rulenode.password"),t.ɵɵelementEnd(),t.ɵɵelement(41,"input",20),t.ɵɵpipe(42,"translate"),t.ɵɵelement(43,"tb-toggle-password",21),t.ɵɵelementEnd()()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(5),t.ɵɵproperty("ngForOf",e.smtpProtocols),t.ɵɵadvance(6),t.ɵɵproperty("ngIf",e.sendEmailConfigForm.get("smtpHost").hasError("required")),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",e.sendEmailConfigForm.get("smtpPort").hasError("required")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",e.sendEmailConfigForm.get("smtpPort").hasError("min")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",e.sendEmailConfigForm.get("smtpPort").hasError("max")),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",e.sendEmailConfigForm.get("timeout").hasError("required")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",e.sendEmailConfigForm.get("timeout").hasError("min")),t.ɵɵadvance(2),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(27,13,"tb.rulenode.enable-tls")," "),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",!0===e.sendEmailConfigForm.get("enableTls").value),t.ɵɵadvance(2),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(31,15,"tb.rulenode.enable-proxy")," "),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",e.sendEmailConfigForm.get("enableProxy").value),t.ɵɵadvance(4),t.ɵɵpropertyInterpolate("placeholder",t.ɵɵpipeBind1(37,17,"tb.rulenode.enter-username")),t.ɵɵadvance(5),t.ɵɵpropertyInterpolate("placeholder",t.ɵɵpipeBind1(42,19,"tb.rulenode.enter-password"))}}e("RestApiCallConfigComponent",Sl);class Ll extends i{constructor(e,t){super(e),this.store=e,this.fb=t,this.smtpProtocols=["smtp","smtps"],this.tlsVersions=["TLSv1","TLSv1.1","TLSv1.2","TLSv1.3"]}configForm(){return this.sendEmailConfigForm}onConfigurationSet(e){this.sendEmailConfigForm=this.fb.group({useSystemSmtpSettings:[!!e&&e.useSystemSmtpSettings,[]],smtpProtocol:[e?e.smtpProtocol:null,[]],smtpHost:[e?e.smtpHost:null,[]],smtpPort:[e?e.smtpPort:null,[]],timeout:[e?e.timeout:null,[]],enableTls:[!!e&&e.enableTls,[]],tlsVersion:[e?e.tlsVersion:null,[]],enableProxy:[!!e&&e.enableProxy,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]]})}validatorTriggers(){return["useSystemSmtpSettings","enableProxy"]}updateValidators(e){const t=this.sendEmailConfigForm.get("useSystemSmtpSettings").value,n=this.sendEmailConfigForm.get("enableProxy").value;t?(this.sendEmailConfigForm.get("smtpProtocol").setValidators([]),this.sendEmailConfigForm.get("smtpHost").setValidators([]),this.sendEmailConfigForm.get("smtpPort").setValidators([]),this.sendEmailConfigForm.get("timeout").setValidators([]),this.sendEmailConfigForm.get("proxyHost").setValidators([]),this.sendEmailConfigForm.get("proxyPort").setValidators([])):(this.sendEmailConfigForm.get("smtpProtocol").setValidators([N.required]),this.sendEmailConfigForm.get("smtpHost").setValidators([N.required]),this.sendEmailConfigForm.get("smtpPort").setValidators([N.required,N.min(1),N.max(65535)]),this.sendEmailConfigForm.get("timeout").setValidators([N.required,N.min(0)]),this.sendEmailConfigForm.get("proxyHost").setValidators(n?[N.required]:[]),this.sendEmailConfigForm.get("proxyPort").setValidators(n?[N.required,N.min(1),N.max(65535)]:[])),this.sendEmailConfigForm.get("smtpProtocol").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpPort").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("timeout").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e})}static{this.ɵfac=function(e){return new(e||Ll)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Ll,selectors:[["tb-external-node-send-email-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:5,vars:5,consts:[[1,"flex","flex-col",3,"formGroup"],["formControlName","useSystemSmtpSettings"],["class","flex flex-col",4,"ngIf"],[1,"flex","flex-col"],[1,"mat-block"],["translate",""],["formControlName","smtpProtocol"],[3,"value",4,"ngFor","ngForOf"],[1,"gt-sm:flex","gt-sm:flex-row","gt-sm:gap-2"],[1,"mat-block","gt-sm:max-w-60%","gt-sm:flex-full"],["required","","matInput","","formControlName","smtpHost"],[4,"ngIf"],[1,"mat-block","gt-sm:max-w-40%","gt-sm:flex-full"],["required","","type","number","step","1","min","1","max","65535","matInput","","formControlName","smtpPort"],["required","","type","number","step","1","min","0","matInput","","formControlName","timeout"],["formControlName","enableTls"],["class","mat-block",4,"ngIf"],["formControlName","enableProxy"],["floatLabel","always",1,"mat-block"],["matInput","","formControlName","username",3,"placeholder"],["matInput","","type","password","formControlName","password",3,"placeholder"],["matSuffix",""],[3,"value"],["formControlName","tlsVersion"],["matInput","","required","","formControlName","proxyHost"],["matInput","","required","","formControlName","proxyPort","type","number","step","1","min","1","max","65535"],["matInput","","formControlName","proxyUser"],["matInput","","formControlName","proxyPassword"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"mat-checkbox",1),t.ɵɵtext(2),t.ɵɵpipe(3,"translate"),t.ɵɵelementEnd(),t.ɵɵtemplate(4,Dl,44,21,"section",2),t.ɵɵelementEnd()),2&e&&(t.ɵɵproperty("formGroup",n.sendEmailConfigForm),t.ɵɵadvance(2),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(3,3,"tb.rulenode.use-system-smtp-settings")," "),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",!1===n.sendEmailConfigForm.get("useSystemSmtpSettings").value))},dependencies:t.ɵɵgetComponentDepsFactory(Ll),encapsulation:2})}}function Pl(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.numbers-to-template-required")," "))}function Rl(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.sms-message-template-required")," "))}function _l(e,n){1&e&&t.ɵɵelement(0,"tb-sms-provider-configuration",9)}e("SendEmailConfigComponent",Ll);class jl extends i{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.sendSmsConfigForm}onConfigurationSet(e){this.sendSmsConfigForm=this.fb.group({numbersToTemplate:[e?e.numbersToTemplate:null,[N.required]],smsMessageTemplate:[e?e.smsMessageTemplate:null,[N.required]],useSystemSmsSettings:[!!e&&e.useSystemSmsSettings,[]],smsProviderConfiguration:[e?e.smsProviderConfiguration:null,[]]})}validatorTriggers(){return["useSystemSmsSettings"]}updateValidators(e){this.sendSmsConfigForm.get("useSystemSmsSettings").value?this.sendSmsConfigForm.get("smsProviderConfiguration").setValidators([]):this.sendSmsConfigForm.get("smsProviderConfiguration").setValidators([N.required]),this.sendSmsConfigForm.get("smsProviderConfiguration").updateValueAndValidity({emitEvent:e})}static{this.ɵfac=function(e){return new(e||jl)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:jl,selectors:[["tb-external-node-send-sms-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:20,vars:13,consts:[[1,"flex","flex-col",3,"formGroup"],["subscriptSizing","dynamic",1,"mat-block"],["translate",""],["required","","matInput","","formControlName","numbersToTemplate"],[4,"ngIf"],[3,"innerHTML"],["required","","matInput","","formControlName","smsMessageTemplate","rows","6"],["formControlName","useSystemSmsSettings"],["formControlName","smsProviderConfiguration","required","",4,"ngIf"],["formControlName","smsProviderConfiguration","required",""]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"mat-form-field",1)(2,"mat-label",2),t.ɵɵtext(3,"tb.rulenode.numbers-to-template"),t.ɵɵelementEnd(),t.ɵɵelement(4,"input",3),t.ɵɵtemplate(5,Pl,3,3,"mat-error",4),t.ɵɵelement(6,"mat-hint",5),t.ɵɵpipe(7,"translate"),t.ɵɵpipe(8,"safe"),t.ɵɵelementEnd(),t.ɵɵelementStart(9,"mat-form-field",1)(10,"mat-label",2),t.ɵɵtext(11,"tb.rulenode.sms-message-template"),t.ɵɵelementEnd(),t.ɵɵelement(12,"textarea",6),t.ɵɵtemplate(13,Rl,3,3,"mat-error",4),t.ɵɵelementStart(14,"mat-hint",2),t.ɵɵtext(15,"tb.rulenode.general-pattern-hint"),t.ɵɵelementEnd()(),t.ɵɵelementStart(16,"mat-checkbox",7),t.ɵɵtext(17),t.ɵɵpipe(18,"translate"),t.ɵɵelementEnd(),t.ɵɵtemplate(19,_l,1,0,"tb-sms-provider-configuration",8),t.ɵɵelementEnd()),2&e&&(t.ɵɵproperty("formGroup",n.sendSmsConfigForm),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",n.sendSmsConfigForm.get("numbersToTemplate").hasError("required")),t.ɵɵadvance(),t.ɵɵproperty("innerHTML",t.ɵɵpipeBind2(8,8,t.ɵɵpipeBind1(7,6,"tb.rulenode.numbers-to-template-hint"),"html"),t.ɵɵsanitizeHtml),t.ɵɵadvance(7),t.ɵɵproperty("ngIf",n.sendSmsConfigForm.get("smsMessageTemplate").hasError("required")),t.ɵɵadvance(4),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(18,11,"tb.rulenode.use-system-sms-settings")," "),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",!1===n.sendSmsConfigForm.get("useSystemSmsSettings").value))},dependencies:t.ɵɵgetComponentDepsFactory(jl),encapsulation:2})}}function Gl(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.message-template-required")," "))}function Kl(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.slack-api-token-required")," "))}function Ul(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-form-field",11)(1,"mat-label",2),t.ɵɵtext(2,"tb.rulenode.slack-api-token"),t.ɵɵelementEnd(),t.ɵɵelement(3,"input",12),t.ɵɵtemplate(4,Kl,3,3,"mat-error",4),t.ɵɵelementEnd()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(4),t.ɵɵproperty("ngIf",e.slackConfigForm.get("botToken").hasError("required"))}}function Hl(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-radio-button",13),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=n.$implicit,r=t.ɵɵnextContext();t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,2,r.slackChanelTypesTranslateMap.get(e))," ")}}e("SendSmsConfigComponent",jl);class zl extends i{constructor(e,t){super(e),this.store=e,this.fb=t,this.slackChanelTypes=Object.keys(F),this.slackChanelTypesTranslateMap=q}configForm(){return this.slackConfigForm}onConfigurationSet(e){this.slackConfigForm=this.fb.group({botToken:[e?e.botToken:null],useSystemSettings:[!!e&&e.useSystemSettings],messageTemplate:[e?e.messageTemplate:null,[N.required]],conversationType:[e?e.conversationType:null,[N.required]],conversation:[e?e.conversation:null,[N.required]]})}validatorTriggers(){return["useSystemSettings"]}updateValidators(e){this.slackConfigForm.get("useSystemSettings").value?this.slackConfigForm.get("botToken").clearValidators():this.slackConfigForm.get("botToken").setValidators([N.required]),this.slackConfigForm.get("botToken").updateValueAndValidity({emitEvent:e})}static{this.ɵfac=function(e){return new(e||zl)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.FormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:zl,selectors:[["tb-external-node-slack-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:18,vars:12,consts:[[1,"flex","flex-col",3,"formGroup"],[1,"mat-block","flex-1"],["translate",""],["required","","matInput","","formControlName","messageTemplate"],[4,"ngIf"],["formControlName","useSystemSettings"],["class","mat-block",4,"ngIf"],[1,"tb-title"],["formControlName","conversationType"],[3,"value",4,"ngFor","ngForOf"],["formControlName","conversation","required","",3,"token","slackChanelType"],[1,"mat-block"],["required","","matInput","","formControlName","botToken"],[3,"value"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"mat-form-field",1)(2,"mat-label",2),t.ɵɵtext(3,"tb.rulenode.message-template"),t.ɵɵelementEnd(),t.ɵɵelement(4,"input",3),t.ɵɵtemplate(5,Gl,3,3,"mat-error",4),t.ɵɵelementStart(6,"mat-hint",2),t.ɵɵtext(7,"tb.rulenode.general-pattern-hint"),t.ɵɵelementEnd()(),t.ɵɵelementStart(8,"mat-checkbox",5),t.ɵɵtext(9),t.ɵɵpipe(10,"translate"),t.ɵɵelementEnd(),t.ɵɵtemplate(11,Ul,5,1,"mat-form-field",6),t.ɵɵelementStart(12,"label",7),t.ɵɵtext(13),t.ɵɵpipe(14,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(15,"mat-radio-group",8),t.ɵɵtemplate(16,Hl,3,4,"mat-radio-button",9),t.ɵɵelementEnd(),t.ɵɵelement(17,"tb-slack-conversation-autocomplete",10),t.ɵɵelementEnd()),2&e&&(t.ɵɵproperty("formGroup",n.slackConfigForm),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",n.slackConfigForm.get("messageTemplate").hasError("required")),t.ɵɵadvance(4),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(10,8,"tb.rulenode.use-system-slack-settings")," "),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",!n.slackConfigForm.get("useSystemSettings").value),t.ɵɵadvance(2),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(14,10,"notification.slack-chanel-type")),t.ɵɵadvance(3),t.ɵɵproperty("ngForOf",n.slackChanelTypes),t.ɵɵadvance(),t.ɵɵproperty("token",n.slackConfigForm.get("useSystemSettings").value?"":n.slackConfigForm.get("botToken").value)("slackChanelType",n.slackConfigForm.get("conversationType").value))},dependencies:t.ɵɵgetComponentDepsFactory(zl),styles:["[_nghost-%COMP%] .tb-title[_ngcontent-%COMP%]{display:block;padding-bottom:6px}[_nghost-%COMP%] .mat-mdc-radio-group{display:flex;flex-direction:row;margin-bottom:22px;gap:12px}[_nghost-%COMP%] .mat-mdc-radio-group .mat-mdc-radio-button{flex:1 1 100%;padding:4px;border:1px solid rgba(0,0,0,.12);border-radius:6px}@media screen and (max-width: 599px){[_nghost-%COMP%] .mat-mdc-radio-group{flex-direction:column}}"]})}}function $l(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.topic-arn-pattern-required")," "))}function Ql(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.aws-access-key-id-required")," "))}function Jl(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.aws-secret-access-key-required")," "))}function Yl(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.aws-region-required")," "))}e("SlackConfigComponent",zl);class Wl extends i{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.snsConfigForm}onConfigurationSet(e){this.snsConfigForm=this.fb.group({topicArnPattern:[e?e.topicArnPattern:null,[N.required]],accessKeyId:[e?e.accessKeyId:null,[N.required]],secretAccessKey:[e?e.secretAccessKey:null,[N.required]],region:[e?e.region:null,[N.required]]})}static{this.ɵfac=function(e){return new(e||Wl)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Wl,selectors:[["tb-external-node-sns-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:23,vars:5,consts:[[1,"flex","flex-col",3,"formGroup"],["subscriptSizing","dynamic",1,"mat-block"],["translate",""],["required","","matInput","","formControlName","topicArnPattern"],[4,"ngIf"],[1,"mat-block"],["required","","matInput","","formControlName","accessKeyId"],["required","","matInput","","formControlName","secretAccessKey"],["required","","matInput","","formControlName","region"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"mat-form-field",1)(2,"mat-label",2),t.ɵɵtext(3,"tb.rulenode.topic-arn-pattern"),t.ɵɵelementEnd(),t.ɵɵelement(4,"input",3),t.ɵɵtemplate(5,$l,3,3,"mat-error",4),t.ɵɵelementStart(6,"mat-hint",2),t.ɵɵtext(7,"tb.rulenode.general-pattern-hint"),t.ɵɵelementEnd()(),t.ɵɵelementStart(8,"mat-form-field",5)(9,"mat-label",2),t.ɵɵtext(10,"tb.rulenode.aws-access-key-id"),t.ɵɵelementEnd(),t.ɵɵelement(11,"input",6),t.ɵɵtemplate(12,Ql,3,3,"mat-error",4),t.ɵɵelementEnd(),t.ɵɵelementStart(13,"mat-form-field",5)(14,"mat-label",2),t.ɵɵtext(15,"tb.rulenode.aws-secret-access-key"),t.ɵɵelementEnd(),t.ɵɵelement(16,"input",7),t.ɵɵtemplate(17,Jl,3,3,"mat-error",4),t.ɵɵelementEnd(),t.ɵɵelementStart(18,"mat-form-field",5)(19,"mat-label",2),t.ɵɵtext(20,"tb.rulenode.aws-region"),t.ɵɵelementEnd(),t.ɵɵelement(21,"input",8),t.ɵɵtemplate(22,Yl,3,3,"mat-error",4),t.ɵɵelementEnd()()),2&e&&(t.ɵɵproperty("formGroup",n.snsConfigForm),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",n.snsConfigForm.get("topicArnPattern").hasError("required")),t.ɵɵadvance(7),t.ɵɵproperty("ngIf",n.snsConfigForm.get("accessKeyId").hasError("required")),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",n.snsConfigForm.get("secretAccessKey").hasError("required")),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",n.snsConfigForm.get("region").hasError("required")))},dependencies:t.ɵɵgetComponentDepsFactory(Wl),encapsulation:2})}}function Xl(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",15),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=n.$implicit,r=t.ɵɵnextContext();t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,2,r.sqsQueueTypeTranslationsMap.get(e))," ")}}function Zl(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.queue-url-pattern-required")," "))}function es(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.min-delay-seconds-message")," "))}function ts(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.max-delay-seconds-message")," "))}function ns(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-form-field",1)(1,"mat-label",2),t.ɵɵtext(2,"tb.rulenode.delay-seconds"),t.ɵɵelementEnd(),t.ɵɵelement(3,"input",16),t.ɵɵtemplate(4,es,3,3,"mat-error",7)(5,ts,3,3,"mat-error",7),t.ɵɵelementEnd()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(4),t.ɵɵproperty("ngIf",e.sqsConfigForm.get("delaySeconds").hasError("min")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",e.sqsConfigForm.get("delaySeconds").hasError("max"))}}function rs(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.aws-access-key-id-required")," "))}function as(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.aws-secret-access-key-required")," "))}function is(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.aws-region-required")," "))}e("SnsConfigComponent",Wl);class os extends i{constructor(e,t){super(e),this.store=e,this.fb=t,this.sqsQueueType=Nt,this.sqsQueueTypes=Object.keys(Nt),this.sqsQueueTypeTranslationsMap=wt}configForm(){return this.sqsConfigForm}onConfigurationSet(e){this.sqsConfigForm=this.fb.group({queueType:[e?e.queueType:null,[N.required]],queueUrlPattern:[e?e.queueUrlPattern:null,[N.required]],delaySeconds:[e?e.delaySeconds:null,[N.min(0),N.max(900)]],messageAttributes:[e?e.messageAttributes:null,[]],accessKeyId:[e?e.accessKeyId:null,[N.required]],secretAccessKey:[e?e.secretAccessKey:null,[N.required]],region:[e?e.region:null,[N.required]]})}static{this.ɵfac=function(e){return new(e||os)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:os,selectors:[["tb-external-node-sqs-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:35,vars:13,consts:[[1,"flex","flex-col",3,"formGroup"],[1,"mat-block"],["translate",""],["formControlName","queueType","required",""],[3,"value",4,"ngFor","ngForOf"],["subscriptSizing","dynamic",1,"mat-block"],["required","","matInput","","formControlName","queueUrlPattern"],[4,"ngIf"],["class","mat-block",4,"ngIf"],["translate","",1,"tb-title"],[1,"tb-hint",3,"innerHTML"],["required","false","formControlName","messageAttributes","keyText","tb.rulenode.name","keyRequiredText","tb.rulenode.name-required","valText","tb.rulenode.value","valRequiredText","tb.rulenode.value-required"],["required","","matInput","","formControlName","accessKeyId"],["required","","matInput","","formControlName","secretAccessKey"],["required","","matInput","","formControlName","region"],[3,"value"],["required","","type","number","min","0","max","900","step","1","matInput","","formControlName","delaySeconds"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"mat-form-field",1)(2,"mat-label",2),t.ɵɵtext(3,"tb.rulenode.queue-type"),t.ɵɵelementEnd(),t.ɵɵelementStart(4,"mat-select",3),t.ɵɵtemplate(5,Xl,3,4,"mat-option",4),t.ɵɵelementEnd()(),t.ɵɵelementStart(6,"mat-form-field",5)(7,"mat-label",2),t.ɵɵtext(8,"tb.rulenode.queue-url-pattern"),t.ɵɵelementEnd(),t.ɵɵelement(9,"input",6),t.ɵɵtemplate(10,Zl,3,3,"mat-error",7),t.ɵɵelementStart(11,"mat-hint",2),t.ɵɵtext(12,"tb.rulenode.general-pattern-hint"),t.ɵɵelementEnd()(),t.ɵɵtemplate(13,ns,6,2,"mat-form-field",8),t.ɵɵelementStart(14,"label",9),t.ɵɵtext(15,"tb.rulenode.message-attributes"),t.ɵɵelementEnd(),t.ɵɵelement(16,"div",10),t.ɵɵpipe(17,"translate"),t.ɵɵpipe(18,"safe"),t.ɵɵelement(19,"tb-kv-map-config-old",11),t.ɵɵelementStart(20,"mat-form-field",1)(21,"mat-label",2),t.ɵɵtext(22,"tb.rulenode.aws-access-key-id"),t.ɵɵelementEnd(),t.ɵɵelement(23,"input",12),t.ɵɵtemplate(24,rs,3,3,"mat-error",7),t.ɵɵelementEnd(),t.ɵɵelementStart(25,"mat-form-field",1)(26,"mat-label",2),t.ɵɵtext(27,"tb.rulenode.aws-secret-access-key"),t.ɵɵelementEnd(),t.ɵɵelement(28,"input",13),t.ɵɵtemplate(29,as,3,3,"mat-error",7),t.ɵɵelementEnd(),t.ɵɵelementStart(30,"mat-form-field",1)(31,"mat-label",2),t.ɵɵtext(32,"tb.rulenode.aws-region"),t.ɵɵelementEnd(),t.ɵɵelement(33,"input",14),t.ɵɵtemplate(34,is,3,3,"mat-error",7),t.ɵɵelementEnd()()),2&e&&(t.ɵɵproperty("formGroup",n.sqsConfigForm),t.ɵɵadvance(5),t.ɵɵproperty("ngForOf",n.sqsQueueTypes),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",n.sqsConfigForm.get("queueUrlPattern").hasError("required")),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",n.sqsConfigForm.get("queueType").value===n.sqsQueueType.STANDARD),t.ɵɵadvance(3),t.ɵɵproperty("innerHTML",t.ɵɵpipeBind2(18,10,t.ɵɵpipeBind1(17,8,"tb.rulenode.message-attributes-hint"),"html"),t.ɵɵsanitizeHtml),t.ɵɵadvance(8),t.ɵɵproperty("ngIf",n.sqsConfigForm.get("accessKeyId").hasError("required")),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",n.sqsConfigForm.get("secretAccessKey").hasError("required")),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",n.sqsConfigForm.get("region").hasError("required")))},dependencies:t.ɵɵgetComponentDepsFactory(os),encapsulation:2})}}function ls(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.function-name-required")," "))}function ss(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.aws-access-key-id-required")," "))}function ps(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.aws-secret-access-key-required")," "))}function ms(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.aws-region-required")," "))}function ds(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.connection-timeout-required")," "))}function us(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.connection-timeout-min")," "))}function cs(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.request-timeout-required")," "))}function fs(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.request-timeout-min")," "))}e("SqsConfigComponent",os);class gs extends i{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.lambdaConfigForm}onConfigurationSet(e){this.lambdaConfigForm=this.fb.group({functionName:[e?e.functionName:null,[N.required]],qualifier:[e?e.qualifier:null,[]],accessKey:[e?e.accessKey:null,[N.required]],secretKey:[e?e.secretKey:null,[N.required]],region:[e?e.region:null,[N.required]],connectionTimeout:[e?e.connectionTimeout:null,[N.required,N.min(0)]],requestTimeout:[e?e.requestTimeout:null,[N.required,N.min(0)]],tellFailureIfFuncThrowsExc:[!!e&&e.tellFailureIfFuncThrowsExc,[]]})}static{this.ɵfac=function(e){return new(e||gs)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:gs,selectors:[["tb-external-node-lambda-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:71,vars:28,consts:[[1,"tb-form-panel","no-padding","no-border",3,"formGroup"],[1,"tb-form-panel","stroked"],[1,"tb-form-row","no-padding","no-border"],["translate","",1,"tb-form-panel-title","tb-required"],["popupHelpLink","rulenode/node-templatization-doc",3,"hintText"],[1,"tb-standard-fields"],[1,"mat-block"],["required","","matInput","","formControlName","functionName"],[4,"ngIf"],["matInput","","formControlName","qualifier"],["translate",""],["expanded","",1,"tb-settings"],["required","","matInput","","formControlName","accessKey"],["required","","matInput","","formControlName","secretKey"],["required","","matInput","","formControlName","region"],[1,"tb-form-panel","stroked","no-padding"],[1,"tb-settings"],[2,"padding","16px"],[1,"tb-form-panel","no-border","no-padding","no-gap",2,"margin-top","0"],[1,"tb-form-row","no-border","same-padding","tb-standard-fields"],[1,"flex"],["type","number","required","","min","0","matInput","","formControlName","connectionTimeout"],["matSuffix","","aria-hidden","false","aria-label","help-icon","color","primary",1,"help-icon","margin-8","cursor-pointer",3,"matTooltip"],["type","number","required","","min","0","matInput","","formControlName","requestTimeout"],[1,"tb-form-row","no-border",2,"margin-bottom","16px",3,"tb-hint-tooltip-icon"],["formControlName","tellFailureIfFuncThrowsExc",1,"mat-slide"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"div",1)(2,"div",2)(3,"div",3),t.ɵɵtext(4,"tb.rulenode.function-configuration"),t.ɵɵelementEnd()(),t.ɵɵelement(5,"tb-example-hint",4),t.ɵɵelementStart(6,"div",5)(7,"mat-form-field",6)(8,"mat-label"),t.ɵɵtext(9),t.ɵɵpipe(10,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(11,"input",7),t.ɵɵtemplate(12,ls,3,3,"mat-error",8),t.ɵɵelementEnd(),t.ɵɵelementStart(13,"mat-form-field",6)(14,"mat-label"),t.ɵɵtext(15),t.ɵɵpipe(16,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(17,"input",9),t.ɵɵelementStart(18,"mat-hint",10),t.ɵɵtext(19,"tb.rulenode.qualifier-hint"),t.ɵɵelementEnd()()()(),t.ɵɵelementStart(20,"div",1)(21,"mat-expansion-panel",11)(22,"mat-expansion-panel-header")(23,"mat-panel-title",3),t.ɵɵtext(24,"tb.rulenode.aws-credentials"),t.ɵɵelementEnd()(),t.ɵɵelementStart(25,"div",5)(26,"mat-form-field",6)(27,"mat-label",10),t.ɵɵtext(28,"tb.rulenode.aws-access-key-id"),t.ɵɵelementEnd(),t.ɵɵelement(29,"input",12),t.ɵɵtemplate(30,ss,3,3,"mat-error",8),t.ɵɵelementEnd(),t.ɵɵelementStart(31,"mat-form-field",6)(32,"mat-label",10),t.ɵɵtext(33,"tb.rulenode.aws-secret-access-key"),t.ɵɵelementEnd(),t.ɵɵelement(34,"input",13),t.ɵɵtemplate(35,ps,3,3,"mat-error",8),t.ɵɵelementEnd(),t.ɵɵelementStart(36,"mat-form-field",6)(37,"mat-label",10),t.ɵɵtext(38,"tb.rulenode.aws-region"),t.ɵɵelementEnd(),t.ɵɵelement(39,"input",14),t.ɵɵtemplate(40,ms,3,3,"mat-error",8),t.ɵɵelementEnd()()()(),t.ɵɵelementStart(41,"div",15)(42,"mat-expansion-panel",16)(43,"mat-expansion-panel-header",17)(44,"mat-panel-title",10),t.ɵɵtext(45,"tb.rulenode.advanced-settings"),t.ɵɵelementEnd()(),t.ɵɵelementStart(46,"div",18)(47,"div",19)(48,"mat-form-field",20)(49,"mat-label",10),t.ɵɵtext(50,"tb.rulenode.connection-timeout"),t.ɵɵelementEnd(),t.ɵɵelement(51,"input",21),t.ɵɵtemplate(52,ds,3,3,"mat-error",8)(53,us,3,3,"mat-error",8),t.ɵɵelementStart(54,"mat-icon",22),t.ɵɵpipe(55,"translate"),t.ɵɵtext(56,"help"),t.ɵɵelementEnd()(),t.ɵɵelementStart(57,"mat-form-field",20)(58,"mat-label",10),t.ɵɵtext(59,"tb.rulenode.request-timeout"),t.ɵɵelementEnd(),t.ɵɵelement(60,"input",23),t.ɵɵtemplate(61,cs,3,3,"mat-error",8)(62,fs,3,3,"mat-error",8),t.ɵɵelementStart(63,"mat-icon",22),t.ɵɵpipe(64,"translate"),t.ɵɵtext(65,"help"),t.ɵɵelementEnd()()(),t.ɵɵelementStart(66,"div",24),t.ɵɵpipe(67,"translate"),t.ɵɵelementStart(68,"mat-slide-toggle",25),t.ɵɵtext(69),t.ɵɵpipe(70,"translate"),t.ɵɵelementEnd()()()()()()),2&e&&(t.ɵɵproperty("formGroup",n.lambdaConfigForm),t.ɵɵadvance(5),t.ɵɵproperty("hintText","tb.rulenode.template-rules-hint"),t.ɵɵadvance(4),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(10,16,"tb.rulenode.function-name")),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",n.lambdaConfigForm.get("functionName").hasError("required")),t.ɵɵadvance(3),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(16,18,"tb.rulenode.qualifier")),t.ɵɵadvance(15),t.ɵɵproperty("ngIf",n.lambdaConfigForm.get("accessKey").hasError("required")),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",n.lambdaConfigForm.get("secretKey").hasError("required")),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",n.lambdaConfigForm.get("region").hasError("required")),t.ɵɵadvance(12),t.ɵɵproperty("ngIf",n.lambdaConfigForm.get("connectionTimeout").hasError("required")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.lambdaConfigForm.get("connectionTimeout").hasError("min")),t.ɵɵadvance(),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(55,20,"tb.rulenode.connection-timeout-hint")),t.ɵɵadvance(7),t.ɵɵproperty("ngIf",n.lambdaConfigForm.get("requestTimeout").hasError("required")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.lambdaConfigForm.get("requestTimeout").hasError("min")),t.ɵɵadvance(),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(64,22,"tb.rulenode.request-timeout-hint")),t.ɵɵadvance(3),t.ɵɵpropertyInterpolate("tb-hint-tooltip-icon",t.ɵɵpipeBind1(67,24,"tb.rulenode.tell-failure-aws-lambda-hint")),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(70,26,"tb.rulenode.tell-failure-aws-lambda")," "))},dependencies:t.ɵɵgetComponentDepsFactory(gs),encapsulation:2})}}e("LambdaConfigComponent",gs);class hs{static{this.ɵfac=function(e){return new(e||hs)}}static{this.ɵmod=t.ɵɵdefineNgModule({type:hs})}static{this.ɵinj=t.ɵɵdefineInjector({imports:[$,S,Q,xi,Wl,os,gs,Zo,_o,Jo,Yo,ll,Sl,Ll,Ao,jl,zl]})}}e("RulenodeCoreConfigExternalModule",hs),("undefined"==typeof ngJitMode||ngJitMode)&&t.ɵɵsetNgModuleScope(hs,{declarations:[Wl,os,gs,Zo,_o,Jo,Yo,ll,Sl,Ll,Ao,jl,zl],imports:[$,S,Q,xi],exports:[Wl,os,gs,Zo,_o,Jo,Yo,ll,Sl,Ll,Ao,jl,zl]});class ys extends i{constructor(e,t){super(e),this.store=e,this.fb=t,this.searchText=""}configForm(){return this.alarmStatusConfigForm}prepareInputConfig(e){return{alarmStatusList:P(e?.alarmStatusList)?e.alarmStatusList:null}}onConfigurationSet(e){this.alarmStatusConfigForm=this.fb.group({alarmStatusList:[e.alarmStatusList,[N.required]]})}static{this.ɵfac=function(e){return new(e||ys)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.FormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:ys,selectors:[["tb-filter-node-check-alarm-status-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:7,vars:2,consts:[[1,"tb-form-panel","stroked",3,"formGroup"],[1,"tb-form-row","no-padding","no-border","space-between"],["translate","",1,"tb-form-panel-title","tb-required"],["translate","",1,"tb-form-panel-hint","tb-error",3,"hidden"],["formControlName","alarmStatusList"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"div",1)(2,"div",2),t.ɵɵtext(3,"tb.rulenode.alarm-status"),t.ɵɵelementEnd(),t.ɵɵelementStart(4,"div",3),t.ɵɵtext(5," tb.rulenode.alarm-required "),t.ɵɵelementEnd()(),t.ɵɵelement(6,"tb-alarm-status-select",4),t.ɵɵelementEnd()),2&e&&(t.ɵɵproperty("formGroup",n.alarmStatusConfigForm),t.ɵɵadvance(4),t.ɵɵproperty("hidden",n.alarmStatusConfigForm.get("alarmStatusList").valid))},dependencies:t.ɵɵgetComponentDepsFactory(ys),encapsulation:2})}}e("CheckAlarmStatusComponent",ys);const bs=e=>({inputName:e});class vs extends i{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.checkMessageConfigForm}prepareInputConfig(e){return{messageNames:P(e?.messageNames)?e.messageNames:[],metadataNames:P(e?.metadataNames)?e.metadataNames:[],checkAllKeys:!!P(e?.checkAllKeys)&&e.checkAllKeys}}prepareOutputConfig(e){return{messageNames:P(e?.messageNames)?e.messageNames:[],metadataNames:P(e?.metadataNames)?e.metadataNames:[],checkAllKeys:e.checkAllKeys}}atLeastOne(e,t=null){return n=>{t||(t=Object.keys(n.controls));return n?.controls&&t.some((t=>!e(n.controls[t])))?null:{atLeastOne:!0}}}onConfigurationSet(e){this.checkMessageConfigForm=this.fb.group({messageNames:[e.messageNames,[]],metadataNames:[e.metadataNames,[]],checkAllKeys:[e.checkAllKeys,[]]},{validators:this.atLeastOne(N.required,["messageNames","metadataNames"])})}get touchedValidationControl(){return["messageNames","metadataNames"].some((e=>this.checkMessageConfigForm.get(e).touched))}static{this.ɵfac=function(e){return new(e||vs)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.FormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:vs,selectors:[["tb-filter-node-check-message-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:25,vars:36,consts:[[1,"tb-form-panel","stroked",3,"formGroup"],[1,"tb-form-row","no-padding","no-border","space-between"],["translate","",1,"tb-form-panel-title","tb-required"],["translate","",1,"tb-form-panel-hint","tb-error",3,"hidden"],["editable","","subscriptSizing","dynamic","formControlName","messageNames",3,"label","placeholder"],["matSuffix","","color","primary","aria-hidden","false","aria-label","help-icon",1,"help-icon","margin-8","cursor-pointer",3,"matTooltip"],["editable","","subscriptSizing","dynamic","formControlName","metadataNames",3,"label","placeholder"],[1,"tb-form-row","no-border","no-padding",3,"tb-hint-tooltip-icon"],["formControlName","checkAllKeys",1,"mat-slide"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"div",1)(2,"div",2),t.ɵɵtext(3,"tb.rulenode.fields-to-check"),t.ɵɵelementEnd(),t.ɵɵelementStart(4,"div",3),t.ɵɵtext(5," tb.rulenode.at-least-one-field-required "),t.ɵɵelementEnd()(),t.ɵɵelementStart(6,"tb-string-items-list",4),t.ɵɵpipe(7,"translate"),t.ɵɵpipe(8,"translate"),t.ɵɵelementStart(9,"mat-icon",5),t.ɵɵpipe(10,"translate"),t.ɵɵpipe(11,"translate"),t.ɵɵtext(12,"help"),t.ɵɵelementEnd()(),t.ɵɵelementStart(13,"tb-string-items-list",6),t.ɵɵpipe(14,"translate"),t.ɵɵpipe(15,"translate"),t.ɵɵelementStart(16,"mat-icon",5),t.ɵɵpipe(17,"translate"),t.ɵɵpipe(18,"translate"),t.ɵɵtext(19,"help"),t.ɵɵelementEnd()(),t.ɵɵelementStart(20,"div",7),t.ɵɵpipe(21,"translate"),t.ɵɵelementStart(22,"mat-slide-toggle",8),t.ɵɵtext(23),t.ɵɵpipe(24,"translate"),t.ɵɵelementEnd()()()),2&e&&(t.ɵɵproperty("formGroup",n.checkMessageConfigForm),t.ɵɵadvance(4),t.ɵɵproperty("hidden",!(n.touchedValidationControl&&n.checkMessageConfigForm.hasError("atLeastOne"))),t.ɵɵadvance(2),t.ɵɵproperty("label",t.ɵɵpipeBind1(7,10,"tb.rulenode.data-keys"))("placeholder",t.ɵɵpipeBind1(8,12,"tb.rulenode.add-message-field")),t.ɵɵadvance(3),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind2(11,16,"tb.rulenode.chip-help",t.ɵɵpureFunction1(32,bs,t.ɵɵpipeBind1(10,14,"tb.rulenode.field-name")))),t.ɵɵadvance(4),t.ɵɵproperty("label",t.ɵɵpipeBind1(14,19,"tb.rulenode.metadata-keys"))("placeholder",t.ɵɵpipeBind1(15,21,"tb.rulenode.add-metadata-field")),t.ɵɵadvance(3),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind2(18,25,"tb.rulenode.chip-help",t.ɵɵpureFunction1(34,bs,t.ɵɵpipeBind1(17,23,"tb.rulenode.field-name")))),t.ɵɵadvance(4),t.ɵɵpropertyInterpolate("tb-hint-tooltip-icon",t.ɵɵpipeBind1(21,28,"tb.rulenode.check-all-keys-tooltip")),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(24,30,"tb.rulenode.check-all-keys")," "))},dependencies:t.ɵɵgetComponentDepsFactory(vs),encapsulation:2})}}function xs(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",10),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementStart(3,"span",11),t.ɵɵtext(4,"tb.rulenode.relations-query-config-direction-suffix"),t.ɵɵelementEnd()()),2&e){const e=n.$implicit,r=t.ɵɵnextContext();t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,2,r.entitySearchDirectionTranslationsMap.get(e))," ")}}function Cs(e,n){if(1&e&&t.ɵɵelement(0,"tb-entity-autocomplete",15),2&e){const e=t.ɵɵnextContext(2);t.ɵɵproperty("entityType",e.checkRelationConfigForm.get("entityType").value)}}function Ss(e,n){if(1&e&&(t.ɵɵelementStart(0,"div",12),t.ɵɵelement(1,"tb-entity-type-select",13),t.ɵɵtemplate(2,Cs,1,1,"tb-entity-autocomplete",14),t.ɵɵelementEnd()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(2),t.ɵɵproperty("ngIf",e.checkRelationConfigForm.get("entityType").value)}}e("CheckMessageConfigComponent",vs);class Ts extends i{constructor(e,t){super(e),this.store=e,this.fb=t,this.entitySearchDirection=Object.values(d),this.entitySearchDirectionTranslationsMap=b}configForm(){return this.checkRelationConfigForm}prepareInputConfig(e){return{checkForSingleEntity:!!P(e?.checkForSingleEntity)&&e.checkForSingleEntity,direction:P(e?.direction)?e.direction:null,entityType:P(e?.entityType)?e.entityType:null,entityId:P(e?.entityId)?e.entityId:null,relationType:P(e?.relationType)?e.relationType:null}}onConfigurationSet(e){this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[e.checkForSingleEntity,[]],direction:[e.direction,[]],entityType:[e.entityType,e&&e.checkForSingleEntity?[N.required]:[]],entityId:[e.entityId,e&&e.checkForSingleEntity?[N.required]:[]],relationType:[e.relationType,[N.required]]})}validatorTriggers(){return["checkForSingleEntity"]}updateValidators(e){const t=this.checkRelationConfigForm.get("checkForSingleEntity").value;this.checkRelationConfigForm.get("entityType").setValidators(t?[N.required]:[]),this.checkRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.checkRelationConfigForm.get("entityId").setValidators(t?[N.required]:[]),this.checkRelationConfigForm.get("entityId").updateValueAndValidity({emitEvent:e})}static{this.ɵfac=function(e){return new(e||Ts)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Ts,selectors:[["tb-filter-node-check-relation-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:17,vars:12,consts:[[1,"tb-form-panel","stroked","no-padding-bottom",3,"formGroup"],["translate","",1,"tb-form-panel-title"],[1,"flex","flex-col"],["hideRequiredMarker","",1,"mat-block"],["formControlName","direction","required",""],[3,"value",4,"ngFor","ngForOf"],["required","","formControlName","relationType"],[1,"tb-form-row","no-border","no-padding","slide-toggle",3,"tb-hint-tooltip-icon"],["formControlName","checkForSingleEntity",1,"mat-slide"],["class","same-width-component-row",4,"ngIf"],[3,"value"],["translate",""],[1,"same-width-component-row"],["showLabel","","required","","formControlName","entityType",2,"min-width","100px","flex","1"],["class","flex-1","required","","formControlName","entityId",3,"entityType",4,"ngIf"],["required","","formControlName","entityId",1,"flex-1",3,"entityType"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"div",1),t.ɵɵtext(2,"tb.rulenode.relation-search-parameters"),t.ɵɵelementEnd(),t.ɵɵelementStart(3,"div",2)(4,"mat-form-field",3)(5,"mat-label"),t.ɵɵtext(6),t.ɵɵpipe(7,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(8,"mat-select",4),t.ɵɵtemplate(9,xs,5,4,"mat-option",5),t.ɵɵelementEnd()(),t.ɵɵelement(10,"tb-relation-type-autocomplete",6),t.ɵɵelementStart(11,"div",7),t.ɵɵpipe(12,"translate"),t.ɵɵelementStart(13,"mat-slide-toggle",8),t.ɵɵtext(14),t.ɵɵpipe(15,"translate"),t.ɵɵelementEnd()(),t.ɵɵtemplate(16,Ss,3,1,"div",9),t.ɵɵelementEnd()()),2&e&&(t.ɵɵproperty("formGroup",n.checkRelationConfigForm),t.ɵɵadvance(6),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(7,6,"relation.direction")),t.ɵɵadvance(3),t.ɵɵproperty("ngForOf",n.entitySearchDirection),t.ɵɵadvance(2),t.ɵɵpropertyInterpolate("tb-hint-tooltip-icon",t.ɵɵpipeBind1(12,8,"tb.rulenode.check-relation-to-specific-entity-tooltip")),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(15,10,"tb.rulenode.check-relation-to-specific-entity")," "),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",n.checkRelationConfigForm.get("checkForSingleEntity").value))},dependencies:t.ɵɵgetComponentDepsFactory(Ts),styles:["[_nghost-%COMP%] .slide-toggle[_ngcontent-%COMP%]{margin-bottom:18px}"]})}}e("CheckRelationConfigComponent",Ts);const Is=e=>({perimeterKeyName:e});function Es(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.latitude-field-name-required")," "))}function Fs(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.longitude-field-name-required")," "))}function qs(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",18),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=n.$implicit,r=t.ɵɵnextContext();t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,2,r.perimeterTypeTranslationMap.get(e))," ")}}function As(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.perimeter-key-name-required")," "))}function ks(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-form-field",19)(1,"mat-label"),t.ɵɵtext(2),t.ɵɵpipe(3,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(4,"input",20),t.ɵɵtemplate(5,As,3,3,"mat-error",6),t.ɵɵelementStart(6,"mat-hint"),t.ɵɵtext(7),t.ɵɵpipe(8,"translate"),t.ɵɵelementEnd()()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(2),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(3,3,"tb.rulenode.perimeter-key-name")),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",e.geoFilterConfigForm.get("perimeterKeyName").hasError("required")),t.ɵɵadvance(2),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(8,5,"tb.rulenode.perimeter-key-name-hint"))}}function Ns(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.circle-center-latitude-required")," "))}function ws(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.circle-center-longitude-required")," "))}function Ms(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.range-required")," "))}function Bs(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",18),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=n.$implicit,r=t.ɵɵnextContext(2);t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,2,r.rangeUnitTranslationMap.get(e))," ")}}function Vs(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.range-units-required")," "))}function Os(e,n){if(1&e&&(t.ɵɵelementStart(0,"div",9)(1,"div",3)(2,"mat-form-field",21)(3,"mat-label"),t.ɵɵtext(4),t.ɵɵpipe(5,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(6,"input",22),t.ɵɵtemplate(7,Ns,3,3,"mat-error",6),t.ɵɵelementEnd(),t.ɵɵelementStart(8,"mat-form-field",21)(9,"mat-label"),t.ɵɵtext(10),t.ɵɵpipe(11,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(12,"input",23),t.ɵɵtemplate(13,ws,3,3,"mat-error",6),t.ɵɵelementEnd()(),t.ɵɵelementStart(14,"div",3)(15,"mat-form-field",21)(16,"mat-label"),t.ɵɵtext(17),t.ɵɵpipe(18,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(19,"input",24),t.ɵɵtemplate(20,Ms,3,3,"mat-error",6),t.ɵɵelementEnd(),t.ɵɵelementStart(21,"mat-form-field",21)(22,"mat-label"),t.ɵɵtext(23),t.ɵɵpipe(24,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(25,"mat-select",25),t.ɵɵtemplate(26,Bs,3,4,"mat-option",12),t.ɵɵelementEnd(),t.ɵɵtemplate(27,Vs,3,3,"mat-error",6),t.ɵɵelementEnd()()()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(4),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(5,9,"tb.rulenode.circle-center-latitude")),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",e.geoFilterConfigForm.get("centerLatitude").hasError("required")),t.ɵɵadvance(3),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(11,11,"tb.rulenode.circle-center-longitude")),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",e.geoFilterConfigForm.get("centerLongitude").hasError("required")),t.ɵɵadvance(4),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(18,13,"tb.rulenode.range")),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",e.geoFilterConfigForm.get("range").hasError("required")),t.ɵɵadvance(3),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(24,15,"tb.rulenode.range-units")),t.ɵɵadvance(3),t.ɵɵproperty("ngForOf",e.rangeUnits),t.ɵɵadvance(),t.ɵɵproperty("ngIf",e.geoFilterConfigForm.get("rangeUnit").hasError("required"))}}function Ds(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.polygon-definition-required")," "))}function Ls(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-form-field",26)(1,"mat-label"),t.ɵɵtext(2),t.ɵɵpipe(3,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(4,"input",27),t.ɵɵelementStart(5,"mat-hint"),t.ɵɵtext(6),t.ɵɵpipe(7,"translate"),t.ɵɵelementEnd(),t.ɵɵtemplate(8,Ds,3,3,"mat-error",6),t.ɵɵelementEnd()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(2),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(3,3,"tb.rulenode.polygon-definition")),t.ɵɵadvance(4),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(7,5,"tb.rulenode.polygon-definition-hint")),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",e.geoFilterConfigForm.get("polygonsDefinition").hasError("required"))}}class Ps extends i{constructor(e,t){super(e),this.store=e,this.fb=t,this.perimeterType=ft,this.perimeterTypes=Object.values(ft),this.perimeterTypeTranslationMap=gt,this.rangeUnits=Object.values(bt),this.rangeUnitTranslationMap=vt,this.defaultPaddingEnable=!0}configForm(){return this.geoFilterConfigForm}prepareInputConfig(e){return{latitudeKeyName:P(e?.latitudeKeyName)?e.latitudeKeyName:null,longitudeKeyName:P(e?.longitudeKeyName)?e.longitudeKeyName:null,perimeterType:P(e?.perimeterType)?e.perimeterType:null,fetchPerimeterInfoFromMessageMetadata:!!P(e?.fetchPerimeterInfoFromMessageMetadata)&&e.fetchPerimeterInfoFromMessageMetadata,perimeterKeyName:P(e?.perimeterKeyName)?e.perimeterKeyName:null,centerLatitude:P(e?.centerLatitude)?e.centerLatitude:null,centerLongitude:P(e?.centerLongitude)?e.centerLongitude:null,range:P(e?.range)?e.range:null,rangeUnit:P(e?.rangeUnit)?e.rangeUnit:null,polygonsDefinition:P(e?.polygonsDefinition)?e.polygonsDefinition:null}}onConfigurationSet(e){this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e.latitudeKeyName,[N.required]],longitudeKeyName:[e.longitudeKeyName,[N.required]],perimeterType:[e.perimeterType,[N.required]],fetchPerimeterInfoFromMessageMetadata:[e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterKeyName:[e.perimeterKeyName,[]],centerLatitude:[e.centerLatitude,[]],centerLongitude:[e.centerLongitude,[]],range:[e.range,[]],rangeUnit:[e.rangeUnit,[]],polygonsDefinition:[e.polygonsDefinition,[]]})}validatorTriggers(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]}updateValidators(e){const t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,n=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterKeyName").setValidators([N.required]):this.geoFilterConfigForm.get("perimeterKeyName").setValidators([]),t||n!==ft.CIRCLE?(this.geoFilterConfigForm.get("centerLatitude").setValidators([]),this.geoFilterConfigForm.get("centerLongitude").setValidators([]),this.geoFilterConfigForm.get("range").setValidators([]),this.geoFilterConfigForm.get("rangeUnit").setValidators([]),this.defaultPaddingEnable=!0):(this.geoFilterConfigForm.get("centerLatitude").setValidators([N.required,N.min(-90),N.max(90)]),this.geoFilterConfigForm.get("centerLongitude").setValidators([N.required,N.min(-180),N.max(180)]),this.geoFilterConfigForm.get("range").setValidators([N.required,N.min(0)]),this.geoFilterConfigForm.get("rangeUnit").setValidators([N.required]),this.defaultPaddingEnable=!1),t||n!==ft.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([N.required]),this.geoFilterConfigForm.get("perimeterKeyName").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})}static{this.ɵfac=function(e){return new(e||Ps)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.FormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Ps,selectors:[["tb-filter-node-gps-geofencing-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:39,vars:32,consts:[[1,"tb-form-panel","no-border","no-padding",3,"formGroup"],[1,"tb-form-panel","stroked"],["translate","",1,"tb-form-panel-title"],[1,"flex","flex-row","gap-4"],[1,"mat-block","max-w-50%","flex-full"],["matInput","","formControlName","latitudeKeyName","required",""],[4,"ngIf"],["matInput","","formControlName","longitudeKeyName","required",""],["translate","",1,"tb-form-hint","tb-primary-fill"],[1,"flex","flex-col"],["hideRequiredMarker","",1,"mat-block","flex-1"],["formControlName","perimeterType"],[3,"value",4,"ngFor","ngForOf"],[1,"tb-form-row","no-border","no-padding","slide-toggle",3,"tb-hint-tooltip-icon"],["formControlName","fetchPerimeterInfoFromMessageMetadata",1,"mat-slide"],["class","mat-block",4,"ngIf"],["class","flex flex-col",4,"ngIf"],["class","mat-block","subscriptSizing","dynamic",4,"ngIf"],[3,"value"],[1,"mat-block"],["matInput","","formControlName","perimeterKeyName","required",""],[1,"flex-1"],["type","number","min","-90","max","90","step","0.1","matInput","","formControlName","centerLatitude","required",""],["type","number","min","-180","max","180","step","0.1","matInput","","formControlName","centerLongitude","required",""],["type","number","min","0","step","0.1","matInput","","formControlName","range","required",""],["formControlName","rangeUnit","required",""],["subscriptSizing","dynamic",1,"mat-block"],["matInput","","formControlName","polygonsDefinition","required",""]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"section",1)(2,"div",2),t.ɵɵtext(3,"tb.rulenode.coordinate-field-names"),t.ɵɵelementEnd(),t.ɵɵelementStart(4,"section")(5,"div",3)(6,"mat-form-field",4)(7,"mat-label"),t.ɵɵtext(8),t.ɵɵpipe(9,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(10,"input",5),t.ɵɵtemplate(11,Es,3,3,"mat-error",6),t.ɵɵelementEnd(),t.ɵɵelementStart(12,"mat-form-field",4)(13,"mat-label"),t.ɵɵtext(14),t.ɵɵpipe(15,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(16,"input",7),t.ɵɵtemplate(17,Fs,3,3,"mat-error",6),t.ɵɵelementEnd()(),t.ɵɵelementStart(18,"div",8),t.ɵɵtext(19,"tb.rulenode.coordinate-field-hint"),t.ɵɵelementEnd()()(),t.ɵɵelementStart(20,"section",1)(21,"div",2),t.ɵɵtext(22,"tb.rulenode.geofence-configuration"),t.ɵɵelementEnd(),t.ɵɵelementStart(23,"section",9)(24,"mat-form-field",10)(25,"mat-label"),t.ɵɵtext(26),t.ɵɵpipe(27,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(28,"mat-select",11),t.ɵɵtemplate(29,qs,3,4,"mat-option",12),t.ɵɵelementEnd()(),t.ɵɵelementStart(30,"div",13),t.ɵɵpipe(31,"translate"),t.ɵɵpipe(32,"translate"),t.ɵɵelementStart(33,"mat-slide-toggle",14),t.ɵɵtext(34),t.ɵɵpipe(35,"translate"),t.ɵɵelementEnd()(),t.ɵɵtemplate(36,ks,9,7,"mat-form-field",15)(37,Os,28,17,"div",16)(38,Ls,9,7,"mat-form-field",17),t.ɵɵelementEnd()()()),2&e&&(t.ɵɵproperty("formGroup",n.geoFilterConfigForm),t.ɵɵadvance(8),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(9,14,"tb.rulenode.latitude-field-name")),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",n.geoFilterConfigForm.get("latitudeKeyName").hasError("required")),t.ɵɵadvance(3),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(15,16,"tb.rulenode.longitude-field-name")),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",n.geoFilterConfigForm.get("longitudeKeyName").hasError("required")),t.ɵɵadvance(3),t.ɵɵclassProp("no-padding-bottom",!n.defaultPaddingEnable),t.ɵɵadvance(6),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(27,18,"tb.rulenode.perimeter-type")),t.ɵɵadvance(3),t.ɵɵproperty("ngForOf",n.perimeterTypes),t.ɵɵadvance(),t.ɵɵpropertyInterpolate("tb-hint-tooltip-icon",n.geoFilterConfigForm.get("perimeterType").value===n.perimeterType.CIRCLE?t.ɵɵpipeBind2(31,20,"tb.rulenode.fetch-circle-parameter-info-from-metadata-hint",t.ɵɵpureFunction1(28,Is,n.geoFilterConfigForm.get("perimeterKeyName").valid?n.geoFilterConfigForm.get("perimeterKeyName").value:"ss_perimeter")):t.ɵɵpipeBind2(32,23,"tb.rulenode.fetch-poligon-parameter-info-from-metadata-hint",t.ɵɵpureFunction1(30,Is,n.geoFilterConfigForm.get("perimeterKeyName").valid?n.geoFilterConfigForm.get("perimeterKeyName").value:"ss_perimeter"))),t.ɵɵadvance(4),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(35,26,"tb.rulenode.fetch-perimeter-info-from-metadata")," "),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",n.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.geoFilterConfigForm.get("perimeterType").value===n.perimeterType.CIRCLE&&!n.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.geoFilterConfigForm.get("perimeterType").value===n.perimeterType.POLYGON&&!n.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value))},dependencies:t.ɵɵgetComponentDepsFactory(Ps),styles:["[_nghost-%COMP%] .slide-toggle[_ngcontent-%COMP%]{margin-bottom:18px}"]})}}e("GpsGeoFilterConfigComponent",Ps);class Rs extends i{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.messageTypeConfigForm}prepareInputConfig(e){return{messageTypes:P(e?.messageTypes)?e.messageTypes:null}}onConfigurationSet(e){this.messageTypeConfigForm=this.fb.group({messageTypes:[e.messageTypes,[N.required]]})}static{this.ɵfac=function(e){return new(e||Rs)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Rs,selectors:[["tb-filter-node-message-type-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:3,vars:4,consts:[[3,"formGroup"],["required","","formControlName","messageTypes",3,"label"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0),t.ɵɵelement(1,"tb-message-types-config",1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵproperty("formGroup",n.messageTypeConfigForm),t.ɵɵadvance(),t.ɵɵproperty("label",t.ɵɵpipeBind1(2,2,"tb.rulenode.select-message-types")))},dependencies:t.ɵɵgetComponentDepsFactory(Rs),encapsulation:2})}}e("MessageTypeConfigComponent",Rs);const _s=e=>({inputName:e});class js extends i{constructor(e,t){super(e),this.store=e,this.fb=t,this.allowedEntityTypes=[u.DEVICE,u.ASSET,u.ENTITY_VIEW,u.TENANT,u.CUSTOMER,u.USER,u.DASHBOARD,u.RULE_CHAIN,u.RULE_NODE,u.EDGE]}configForm(){return this.originatorTypeConfigForm}prepareInputConfig(e){return{originatorTypes:P(e?.originatorTypes)?e.originatorTypes:null}}onConfigurationSet(e){this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e.originatorTypes,[N.required]]})}static{this.ɵfac=function(e){return new(e||js)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:js,selectors:[["tb-filter-node-originator-type-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:9,vars:20,consts:[[3,"formGroup"],["formControlName","originatorTypes","required","",1,"flex-1",3,"allowedEntityTypes","ignoreAuthorityFilter","emptyInputPlaceholder","filledInputPlaceholder","label"],["matSuffix","","aria-hidden","false","aria-label","help-icon","color","primary",1,"help-icon","margin-8","cursor-pointer",3,"matTooltip"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"tb-entity-type-list",1),t.ɵɵpipe(2,"translate"),t.ɵɵpipe(3,"translate"),t.ɵɵpipe(4,"translate"),t.ɵɵelementStart(5,"mat-icon",2),t.ɵɵpipe(6,"translate"),t.ɵɵpipe(7,"translate"),t.ɵɵtext(8,"help"),t.ɵɵelementEnd()()()),2&e&&(t.ɵɵproperty("formGroup",n.originatorTypeConfigForm),t.ɵɵadvance(),t.ɵɵproperty("allowedEntityTypes",n.allowedEntityTypes)("ignoreAuthorityFilter",!0)("emptyInputPlaceholder",t.ɵɵpipeBind1(2,7,"tb.rulenode.add-entity-type"))("filledInputPlaceholder",t.ɵɵpipeBind1(3,9,"tb.rulenode.add-entity-type"))("label",t.ɵɵpipeBind1(4,11,"tb.rulenode.select-entity-types")),t.ɵɵadvance(4),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind2(7,15,"tb.rulenode.chip-help",t.ɵɵpureFunction1(18,_s,t.ɵɵpipeBind1(6,13,"tb.rulenode.entity-type")))))},dependencies:t.ɵɵgetComponentDepsFactory(js),encapsulation:2})}}e("OriginatorTypeConfigComponent",js);const Gs=["jsFuncComponent"],Ks=["tbelFuncComponent"],Us=()=>["msg","metadata","msgType"];function Hs(e,n){1&e&&t.ɵɵelement(0,"tb-script-lang",7)}function zs(e,n){if(1&e){const e=t.ɵɵgetCurrentView();t.ɵɵelementStart(0,"tb-js-func",8,0)(2,"button",9),t.ɵɵpipe(3,"translate"),t.ɵɵlistener("click",(function(){t.ɵɵrestoreView(e);const n=t.ɵɵnextContext();return t.ɵɵresetView(n.testScript())})),t.ɵɵelementStart(4,"mat-icon",10),t.ɵɵtext(5,"bug_report"),t.ɵɵelementEnd()()()}if(2&e){const e=t.ɵɵnextContext();t.ɵɵproperty("functionArgs",t.ɵɵpureFunction0(4,Us)),t.ɵɵadvance(2),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(3,2,e.testScriptLabel))}}function $s(e,n){if(1&e){const e=t.ɵɵgetCurrentView();t.ɵɵelementStart(0,"tb-js-func",11,1)(2,"button",9),t.ɵɵpipe(3,"translate"),t.ɵɵlistener("click",(function(){t.ɵɵrestoreView(e);const n=t.ɵɵnextContext();return t.ɵɵresetView(n.testScript())})),t.ɵɵelementStart(4,"mat-icon",10),t.ɵɵtext(5,"bug_report"),t.ɵɵelementEnd()()()}if(2&e){const e=t.ɵɵnextContext();t.ɵɵproperty("functionArgs",t.ɵɵpureFunction0(6,Us))("disableUndefinedCheck",!0)("scriptLanguage",e.scriptLanguage.TBEL),t.ɵɵadvance(2),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(3,4,e.testScriptLabel))}}class Qs extends i{constructor(e,t,r,a){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=r,this.translate=a,this.tbelEnabled=D(this.store).tbelEnabled,this.scriptLanguage=s,this.changeScript=new n,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-filter-function"}configForm(){return this.scriptConfigForm}onConfigurationSet(e){this.scriptConfigForm=this.fb.group({scriptLang:[e.scriptLang,[N.required]],jsScript:[e.jsScript,[]],tbelScript:[e.tbelScript,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.scriptConfigForm.get("scriptLang").value;t!==s.TBEL||this.tbelEnabled||(t=s.JS,this.scriptConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.scriptConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.scriptConfigForm.get("jsScript").setValidators(t===s.JS?[N.required]:[]),this.scriptConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.scriptConfigForm.get("tbelScript").setValidators(t===s.TBEL?[N.required]:[]),this.scriptConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=s.JS)),{scriptLang:P(e?.scriptLang)?e.scriptLang:s.JS,jsScript:P(e?.jsScript)?e.jsScript:null,tbelScript:P(e?.tbelScript)?e.tbelScript:null}}testScript(e){const t=this.scriptConfigForm.get("scriptLang").value,n=t===s.JS?"jsScript":"tbelScript",r=t===s.JS?"rulenode/filter_node_script_fn":"rulenode/tbel/filter_node_script_fn",a=this.scriptConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(a,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.scriptConfigForm.get(n).setValue(e),this.changeScript.emit())}))}onValidate(){this.scriptConfigForm.get("scriptLang").value===s.JS&&this.jsFuncComponent.validateOnSubmit()}static{this.ɵfac=function(e){return new(e||Qs)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder),t.ɵɵdirectiveInject(L.NodeScriptTestService),t.ɵɵdirectiveInject(K.TranslateService))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Qs,selectors:[["tb-filter-node-script-config"]],viewQuery:function(e,n){if(1&e&&(t.ɵɵviewQuery(Gs,5),t.ɵɵviewQuery(Ks,5)),2&e){let e;t.ɵɵqueryRefresh(e=t.ɵɵloadQuery())&&(n.jsFuncComponent=e.first),t.ɵɵqueryRefresh(e=t.ɵɵloadQuery())&&(n.tbelFuncComponent=e.first)}},features:[t.ɵɵInheritDefinitionFeature],decls:7,vars:7,consts:[["jsFuncComponent",""],["tbelFuncComponent",""],[3,"formGroup"],["formControlName","scriptLang",4,"ngIf"],["formControlName","jsScript","functionName","Filter","helpId","rulenode/filter_node_script_fn","noValidate","true",3,"functionArgs",4,"ngIf"],["formControlName","tbelScript","functionName","Filter","helpId","rulenode/tbel/filter_node_script_fn","noValidate","true",3,"functionArgs","disableUndefinedCheck","scriptLanguage",4,"ngIf"],["mat-button","","mat-raised-button","","color","primary",3,"click"],["formControlName","scriptLang"],["formControlName","jsScript","functionName","Filter","helpId","rulenode/filter_node_script_fn","noValidate","true",3,"functionArgs"],["toolbarSuffixButton","","mat-icon-button","","matTooltipPosition","above",1,"tb-mat-32",3,"click","matTooltip"],["color","primary",1,"material-icons"],["formControlName","tbelScript","functionName","Filter","helpId","rulenode/tbel/filter_node_script_fn","noValidate","true",3,"functionArgs","disableUndefinedCheck","scriptLanguage"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",2),t.ɵɵtemplate(1,Hs,1,0,"tb-script-lang",3)(2,zs,6,5,"tb-js-func",4)(3,$s,6,7,"tb-js-func",5),t.ɵɵelementStart(4,"button",6),t.ɵɵlistener("click",(function(){return n.testScript()})),t.ɵɵtext(5),t.ɵɵpipe(6,"translate"),t.ɵɵelementEnd()()),2&e&&(t.ɵɵproperty("formGroup",n.scriptConfigForm),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.tbelEnabled),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.scriptConfigForm.get("scriptLang").value===n.scriptLanguage.JS),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.scriptConfigForm.get("scriptLang").value===n.scriptLanguage.TBEL),t.ɵɵadvance(2),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(6,5,n.testScriptLabel)," "))},dependencies:t.ɵɵgetComponentDepsFactory(Qs),encapsulation:2})}}e("ScriptConfigComponent",Qs);const Js=["jsFuncComponent"],Ys=["tbelFuncComponent"],Ws=()=>["msg","metadata","msgType"];function Xs(e,n){1&e&&t.ɵɵelement(0,"tb-script-lang",7)}function Zs(e,n){if(1&e){const e=t.ɵɵgetCurrentView();t.ɵɵelementStart(0,"tb-js-func",8,0)(2,"button",9),t.ɵɵpipe(3,"translate"),t.ɵɵlistener("click",(function(){t.ɵɵrestoreView(e);const n=t.ɵɵnextContext();return t.ɵɵresetView(n.testScript())})),t.ɵɵelementStart(4,"mat-icon",10),t.ɵɵtext(5,"bug_report"),t.ɵɵelementEnd()()()}if(2&e){const e=t.ɵɵnextContext();t.ɵɵproperty("functionArgs",t.ɵɵpureFunction0(4,Ws)),t.ɵɵadvance(2),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(3,2,e.testScriptLabel))}}function ep(e,n){if(1&e){const e=t.ɵɵgetCurrentView();t.ɵɵelementStart(0,"tb-js-func",11,1)(2,"button",9),t.ɵɵpipe(3,"translate"),t.ɵɵlistener("click",(function(){t.ɵɵrestoreView(e);const n=t.ɵɵnextContext();return t.ɵɵresetView(n.testScript())})),t.ɵɵelementStart(4,"mat-icon",10),t.ɵɵtext(5,"bug_report"),t.ɵɵelementEnd()()()}if(2&e){const e=t.ɵɵnextContext();t.ɵɵproperty("functionArgs",t.ɵɵpureFunction0(6,Ws))("disableUndefinedCheck",!0)("scriptLanguage",e.scriptLanguage.TBEL),t.ɵɵadvance(2),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(3,4,e.testScriptLabel))}}class tp extends i{constructor(e,t,r,a){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=r,this.translate=a,this.tbelEnabled=D(this.store).tbelEnabled,this.scriptLanguage=s,this.changeScript=new n,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-switch-function"}configForm(){return this.switchConfigForm}onConfigurationSet(e){this.switchConfigForm=this.fb.group({scriptLang:[e.scriptLang,[N.required]],jsScript:[e.jsScript,[]],tbelScript:[e.tbelScript,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.switchConfigForm.get("scriptLang").value;t!==s.TBEL||this.tbelEnabled||(t=s.JS,this.switchConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.switchConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.switchConfigForm.get("jsScript").setValidators(t===s.JS?[N.required]:[]),this.switchConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.switchConfigForm.get("tbelScript").setValidators(t===s.TBEL?[N.required]:[]),this.switchConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=s.JS)),{scriptLang:P(e?.scriptLang)?e.scriptLang:s.JS,jsScript:P(e?.jsScript)?e.jsScript:null,tbelScript:P(e?.tbelScript)?e.tbelScript:null}}testScript(e){const t=this.switchConfigForm.get("scriptLang").value,n=t===s.JS?"jsScript":"tbelScript",r=t===s.JS?"rulenode/switch_node_script_fn":"rulenode/tbel/switch_node_script_fn",a=this.switchConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(a,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.switchConfigForm.get(n).setValue(e),this.changeScript.emit())}))}onValidate(){this.switchConfigForm.get("scriptLang").value===s.JS&&this.jsFuncComponent.validateOnSubmit()}static{this.ɵfac=function(e){return new(e||tp)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder),t.ɵɵdirectiveInject(L.NodeScriptTestService),t.ɵɵdirectiveInject(K.TranslateService))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:tp,selectors:[["tb-filter-node-switch-config"]],viewQuery:function(e,n){if(1&e&&(t.ɵɵviewQuery(Js,5),t.ɵɵviewQuery(Ys,5)),2&e){let e;t.ɵɵqueryRefresh(e=t.ɵɵloadQuery())&&(n.jsFuncComponent=e.first),t.ɵɵqueryRefresh(e=t.ɵɵloadQuery())&&(n.tbelFuncComponent=e.first)}},features:[t.ɵɵInheritDefinitionFeature],decls:7,vars:7,consts:[["jsFuncComponent",""],["tbelFuncComponent",""],[3,"formGroup"],["formControlName","scriptLang",4,"ngIf"],["formControlName","jsScript","functionName","Switch","helpId","rulenode/switch_node_script_fn","noValidate","true",3,"functionArgs",4,"ngIf"],["formControlName","tbelScript","functionName","Switch","helpId","rulenode/tbel/switch_node_script_fn","noValidate","true",3,"functionArgs","disableUndefinedCheck","scriptLanguage",4,"ngIf"],["mat-button","","mat-raised-button","","color","primary",3,"click"],["formControlName","scriptLang"],["formControlName","jsScript","functionName","Switch","helpId","rulenode/switch_node_script_fn","noValidate","true",3,"functionArgs"],["toolbarSuffixButton","","mat-icon-button","","matTooltipPosition","above",1,"tb-mat-32",3,"click","matTooltip"],["color","primary",1,"material-icons"],["formControlName","tbelScript","functionName","Switch","helpId","rulenode/tbel/switch_node_script_fn","noValidate","true",3,"functionArgs","disableUndefinedCheck","scriptLanguage"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",2),t.ɵɵtemplate(1,Xs,1,0,"tb-script-lang",3)(2,Zs,6,5,"tb-js-func",4)(3,ep,6,7,"tb-js-func",5),t.ɵɵelementStart(4,"button",6),t.ɵɵlistener("click",(function(){return n.testScript()})),t.ɵɵtext(5),t.ɵɵpipe(6,"translate"),t.ɵɵelementEnd()()),2&e&&(t.ɵɵproperty("formGroup",n.switchConfigForm),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.tbelEnabled),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.switchConfigForm.get("scriptLang").value===n.scriptLanguage.JS),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.switchConfigForm.get("scriptLang").value===n.scriptLanguage.TBEL),t.ɵɵadvance(2),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(6,5,n.testScriptLabel)," "))},dependencies:t.ɵɵgetComponentDepsFactory(tp),encapsulation:2})}}e("SwitchConfigComponent",tp);class np{static{this.ɵfac=function(e){return new(e||np)}}static{this.ɵmod=t.ɵɵdefineNgModule({type:np})}static{this.ɵinj=t.ɵɵdefineInjector({imports:[$,S,xi,vs,Ts,Ps,Rs,js,Qs,tp,ys]})}}function rp(e,n){if(1&e&&(t.ɵɵelementStart(0,"span"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,e.originatorSourceTranslationMap.get(e.changeOriginatorConfigForm.get("originatorSource").value))," ")}}function ap(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",8)(1,"span",9),t.ɵɵtext(2),t.ɵɵpipe(3,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(4,"br"),t.ɵɵelementStart(5,"small",10),t.ɵɵtext(6),t.ɵɵpipe(7,"translate"),t.ɵɵelementEnd()()),2&e){const e=n.$implicit,r=t.ɵɵnextContext();t.ɵɵproperty("value",e),t.ɵɵadvance(2),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(3,3,r.originatorSourceTranslationMap.get(e))," "),t.ɵɵadvance(4),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(7,5,r.originatorSourceDescTranslationMap.get(e))," ")}}function ip(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.entity-name-pattern-required")," "))}function op(e,n){if(1&e&&(t.ɵɵelementStart(0,"div",11),t.ɵɵelement(1,"tb-example-hint",12),t.ɵɵelementStart(2,"div",13),t.ɵɵelement(3,"tb-entity-type-select",14),t.ɵɵelementStart(4,"mat-form-field",15)(5,"mat-label",2),t.ɵɵtext(6,"tb.rulenode.entity-name-pattern"),t.ɵɵelementEnd(),t.ɵɵelement(7,"input",16),t.ɵɵtemplate(8,ip,3,3,"mat-error",4),t.ɵɵelementEnd()()()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(),t.ɵɵproperty("hintText","tb.rulenode.entity-name-pattern-hint"),t.ɵɵadvance(2),t.ɵɵproperty("allowedEntityTypes",e.allowedEntityTypes),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",e.changeOriginatorConfigForm.get("entityNamePattern").hasError("required")||e.changeOriginatorConfigForm.get("entityNamePattern").hasError("pattern"))}}function lp(e,n){1&e&&t.ɵɵelement(0,"tb-relations-query-config",17)}e("RuleNodeCoreConfigFilterModule",np),("undefined"==typeof ngJitMode||ngJitMode)&&t.ɵɵsetNgModuleScope(np,{declarations:[vs,Ts,Ps,Rs,js,Qs,tp,ys],imports:[$,S,xi],exports:[vs,Ts,Ps,Rs,js,Qs,tp,ys]});class sp extends i{constructor(e,t){super(e),this.store=e,this.fb=t,this.originatorSource=pt,this.originatorSources=Object.keys(pt),this.originatorSourceTranslationMap=mt,this.originatorSourceDescTranslationMap=dt,this.allowedEntityTypes=[u.DEVICE,u.ASSET,u.ENTITY_VIEW,u.USER,u.EDGE]}configForm(){return this.changeOriginatorConfigForm}onConfigurationSet(e){this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[N.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationsQuery:[e?e.relationsQuery:null,[]]})}validatorTriggers(){return["originatorSource"]}updateValidators(e){const t=this.changeOriginatorConfigForm.get("originatorSource").value;t===pt.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([N.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),t===pt.ENTITY?(this.changeOriginatorConfigForm.get("entityType").setValidators([N.required]),this.changeOriginatorConfigForm.get("entityNamePattern").setValidators([N.required,N.pattern(/.*\S.*/)])):(this.changeOriginatorConfigForm.get("entityType").patchValue(null,{emitEvent:e}),this.changeOriginatorConfigForm.get("entityNamePattern").patchValue(null,{emitEvent:e}),this.changeOriginatorConfigForm.get("entityType").setValidators([]),this.changeOriginatorConfigForm.get("entityNamePattern").setValidators([])),this.changeOriginatorConfigForm.get("relationsQuery").updateValueAndValidity({emitEvent:e}),this.changeOriginatorConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.changeOriginatorConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})}static{this.ɵfac=function(e){return new(e||sp)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.FormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:sp,selectors:[["tb-transformation-node-change-originator-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:10,vars:5,consts:[[1,"tb-form-panel","no-border","no-padding",3,"formGroup"],["subscriptSizing","dynamic","hideRequiredMarker","",1,"mat-block"],["translate",""],["formControlName","originatorSource","required",""],[4,"ngIf"],[3,"value",4,"ngFor","ngForOf"],["class","tb-form-panel stroked no-padding-bottom",4,"ngIf"],["required","","formControlName","relationsQuery",4,"ngIf"],[3,"value"],["matListItemTitle",""],["matListItemMeta","",2,"color","inherit"],[1,"tb-form-panel","stroked","no-padding-bottom"],["popupHelpLink","rulenode/change_originator_node_fields_templatization",3,"hintText"],[1,"tb-form-row","no-border","no-padding","tb-standard-fields"],["showLabel","","required","","formControlName","entityType",1,"mat-mdc-form-field","flex",3,"allowedEntityTypes"],[1,"flex"],["required","","matInput","","formControlName","entityNamePattern"],["required","","formControlName","relationsQuery"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"mat-form-field",1)(2,"mat-label",2),t.ɵɵtext(3,"tb.rulenode.new-originator"),t.ɵɵelementEnd(),t.ɵɵelementStart(4,"mat-select",3)(5,"mat-select-trigger"),t.ɵɵtemplate(6,rp,3,3,"span",4),t.ɵɵelementEnd(),t.ɵɵtemplate(7,ap,8,7,"mat-option",5),t.ɵɵelementEnd()(),t.ɵɵtemplate(8,op,9,3,"div",6)(9,lp,1,0,"tb-relations-query-config",7),t.ɵɵelementEnd()),2&e&&(t.ɵɵproperty("formGroup",n.changeOriginatorConfigForm),t.ɵɵadvance(6),t.ɵɵproperty("ngIf",n.originatorSourceTranslationMap.get(n.changeOriginatorConfigForm.get("originatorSource").value)),t.ɵɵadvance(),t.ɵɵproperty("ngForOf",n.originatorSources),t.ɵɵadvance(),t.ɵɵproperty("ngIf","ENTITY"===n.changeOriginatorConfigForm.get("originatorSource").value),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.changeOriginatorConfigForm.get("originatorSource").value===n.originatorSource.RELATED))},dependencies:t.ɵɵgetComponentDepsFactory(sp),encapsulation:2})}}e("ChangeOriginatorConfigComponent",sp);const pp=["jsFuncComponent"],mp=["tbelFuncComponent"],dp=()=>["msg","metadata","msgType"];function up(e,n){1&e&&t.ɵɵelement(0,"tb-script-lang",7)}function cp(e,n){if(1&e){const e=t.ɵɵgetCurrentView();t.ɵɵelementStart(0,"tb-js-func",8,0)(2,"button",9),t.ɵɵpipe(3,"translate"),t.ɵɵlistener("click",(function(){t.ɵɵrestoreView(e);const n=t.ɵɵnextContext();return t.ɵɵresetView(n.testScript())})),t.ɵɵelementStart(4,"mat-icon",10),t.ɵɵtext(5,"bug_report"),t.ɵɵelementEnd()()()}if(2&e){const e=t.ɵɵnextContext();t.ɵɵproperty("functionArgs",t.ɵɵpureFunction0(4,dp)),t.ɵɵadvance(2),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(3,2,e.testScriptLabel))}}function fp(e,n){if(1&e){const e=t.ɵɵgetCurrentView();t.ɵɵelementStart(0,"tb-js-func",11,1)(2,"button",9),t.ɵɵpipe(3,"translate"),t.ɵɵlistener("click",(function(){t.ɵɵrestoreView(e);const n=t.ɵɵnextContext();return t.ɵɵresetView(n.testScript())})),t.ɵɵelementStart(4,"mat-icon",10),t.ɵɵtext(5,"bug_report"),t.ɵɵelementEnd()()()}if(2&e){const e=t.ɵɵnextContext();t.ɵɵproperty("functionArgs",t.ɵɵpureFunction0(6,dp))("disableUndefinedCheck",!0)("scriptLanguage",e.scriptLanguage.TBEL),t.ɵɵadvance(2),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(3,4,e.testScriptLabel))}}class gp extends i{constructor(e,t,r,a){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=r,this.translate=a,this.tbelEnabled=D(this.store).tbelEnabled,this.scriptLanguage=s,this.changeScript=new n,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-transformer-function"}configForm(){return this.scriptConfigForm}onConfigurationSet(e){this.scriptConfigForm=this.fb.group({scriptLang:[e?e.scriptLang:s.JS,[N.required]],jsScript:[e?e.jsScript:null,[N.required]],tbelScript:[e?e.tbelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.scriptConfigForm.get("scriptLang").value;t!==s.TBEL||this.tbelEnabled||(t=s.JS,this.scriptConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.scriptConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.scriptConfigForm.get("jsScript").setValidators(t===s.JS?[N.required]:[]),this.scriptConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.scriptConfigForm.get("tbelScript").setValidators(t===s.TBEL?[N.required]:[]),this.scriptConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=s.JS)),e}testScript(e){const t=this.scriptConfigForm.get("scriptLang").value,n=t===s.JS?"jsScript":"tbelScript",r=t===s.JS?"rulenode/transformation_node_script_fn":"rulenode/tbel/transformation_node_script_fn",a=this.scriptConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(a,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.scriptConfigForm.get(n).setValue(e),this.changeScript.emit())}))}onValidate(){this.scriptConfigForm.get("scriptLang").value===s.JS&&this.jsFuncComponent.validateOnSubmit()}static{this.ɵfac=function(e){return new(e||gp)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.FormBuilder),t.ɵɵdirectiveInject(L.NodeScriptTestService),t.ɵɵdirectiveInject(K.TranslateService))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:gp,selectors:[["tb-transformation-node-script-config"]],viewQuery:function(e,n){if(1&e&&(t.ɵɵviewQuery(pp,5),t.ɵɵviewQuery(mp,5)),2&e){let e;t.ɵɵqueryRefresh(e=t.ɵɵloadQuery())&&(n.jsFuncComponent=e.first),t.ɵɵqueryRefresh(e=t.ɵɵloadQuery())&&(n.tbelFuncComponent=e.first)}},features:[t.ɵɵInheritDefinitionFeature],decls:8,vars:7,consts:[["jsFuncComponent",""],["tbelFuncComponent",""],[3,"formGroup"],["formControlName","scriptLang",4,"ngIf"],["formControlName","jsScript","functionName","Transform","helpId","rulenode/transformation_node_script_fn","noValidate","true",3,"functionArgs",4,"ngIf"],["formControlName","tbelScript","functionName","Transform","helpId","rulenode/tbel/transformation_node_script_fn","noValidate","true",3,"functionArgs","disableUndefinedCheck","scriptLanguage",4,"ngIf"],["mat-button","","mat-raised-button","","color","primary",3,"click"],["formControlName","scriptLang"],["formControlName","jsScript","functionName","Transform","helpId","rulenode/transformation_node_script_fn","noValidate","true",3,"functionArgs"],["toolbarSuffixButton","","mat-icon-button","","matTooltipPosition","above",1,"tb-mat-32",3,"click","matTooltip"],["color","primary",1,"material-icons"],["formControlName","tbelScript","functionName","Transform","helpId","rulenode/tbel/transformation_node_script_fn","noValidate","true",3,"functionArgs","disableUndefinedCheck","scriptLanguage"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",2),t.ɵɵtemplate(1,up,1,0,"tb-script-lang",3)(2,cp,6,5,"tb-js-func",4)(3,fp,6,7,"tb-js-func",5),t.ɵɵelementStart(4,"div")(5,"button",6),t.ɵɵlistener("click",(function(){return n.testScript()})),t.ɵɵtext(6),t.ɵɵpipe(7,"translate"),t.ɵɵelementEnd()()()),2&e&&(t.ɵɵproperty("formGroup",n.scriptConfigForm),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.tbelEnabled),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.scriptConfigForm.get("scriptLang").value===n.scriptLanguage.JS),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.scriptConfigForm.get("scriptLang").value===n.scriptLanguage.TBEL),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(7,5,n.testScriptLabel)," "))},dependencies:t.ɵɵgetComponentDepsFactory(gp),encapsulation:2})}}e("TransformScriptConfigComponent",gp);const hp=()=>({maxWidth:"820px"});function yp(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.from-template-required")," "))}function bp(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.to-template-required")," "))}function vp(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.subject-template-required")," "))}function xp(e,n){if(1&e&&(t.ɵɵelementStart(0,"span"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=t.ɵɵnextContext();t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,e.getBodyTypeName())," ")}}function Cp(e,n){if(1&e&&(t.ɵɵelementStart(0,"mat-option",24)(1,"span",25),t.ɵɵtext(2),t.ɵɵpipe(3,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(4,"br"),t.ɵɵelementStart(5,"small",26),t.ɵɵtext(6),t.ɵɵpipe(7,"translate"),t.ɵɵelementEnd()()),2&e){const e=n.$implicit;t.ɵɵproperty("value",e.value),t.ɵɵadvance(2),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(3,3,e.name)," "),t.ɵɵadvance(4),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(7,5,e.description)," ")}}function Sp(e,n){1&e&&(t.ɵɵelementStart(0,"mat-form-field",18)(1,"mat-label",5),t.ɵɵtext(2,"tb.rulenode.body-type-template"),t.ɵɵelementEnd(),t.ɵɵelement(3,"input",27),t.ɵɵelementStart(4,"mat-hint",5),t.ɵɵtext(5,"tb.mail-body-type.after-template-evaluation-hint"),t.ɵɵelementEnd()())}function Tp(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.body-template-required")," "))}class Ip extends i{constructor(e,t){super(e),this.store=e,this.fb=t,this.mailBodyTypes=[{name:"tb.mail-body-type.plain-text",description:"tb.mail-body-type.plain-text-description",value:"false"},{name:"tb.mail-body-type.html",description:"tb.mail-body-type.html-text-description",value:"true"},{name:"tb.mail-body-type.use-body-type-template",description:"tb.mail-body-type.dynamic-text-description",value:"dynamic"}]}configForm(){return this.toEmailConfigForm}onConfigurationSet(e){this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[N.required]],toTemplate:[e?e.toTemplate:null,[N.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[N.required]],mailBodyType:[e?e.mailBodyType:null],isHtmlTemplate:[e?e.isHtmlTemplate:null,[N.required]],bodyTemplate:[e?e.bodyTemplate:null,[N.required]]})}prepareInputConfig(e){return{fromTemplate:P(e?.fromTemplate)?e.fromTemplate:null,toTemplate:P(e?.toTemplate)?e.toTemplate:null,ccTemplate:P(e?.ccTemplate)?e.ccTemplate:null,bccTemplate:P(e?.bccTemplate)?e.bccTemplate:null,subjectTemplate:P(e?.subjectTemplate)?e.subjectTemplate:null,mailBodyType:P(e?.mailBodyType)?e.mailBodyType:null,isHtmlTemplate:P(e?.isHtmlTemplate)?e.isHtmlTemplate:null,bodyTemplate:P(e?.bodyTemplate)?e.bodyTemplate:null}}updateValidators(e){"dynamic"===this.toEmailConfigForm.get("mailBodyType").value?this.toEmailConfigForm.get("isHtmlTemplate").enable({emitEvent:!1}):this.toEmailConfigForm.get("isHtmlTemplate").disable({emitEvent:!1}),this.toEmailConfigForm.get("isHtmlTemplate").updateValueAndValidity({emitEvent:e})}validatorTriggers(){return["mailBodyType"]}getBodyTypeName(){return this.mailBodyTypes.find((e=>e.value===this.toEmailConfigForm.get("mailBodyType").value)).name}static{this.ɵfac=function(e){return new(e||Ip)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.FormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Ip,selectors:[["tb-transformation-node-to-email-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:61,vars:23,consts:[[1,"tb-form-panel","no-border","no-padding",3,"formGroup"],[1,"tb-form-panel","stroked"],["translate","",1,"tb-form-panel-title"],[1,"tb-form-row","no-border","no-padding","tb-standard-fields"],["subscriptSizing","dynamic",1,"flex"],["translate",""],["required","","matInput","","formControlName","fromTemplate"],["align","start"],["align","end"],[1,"input-bottom-double-hint"],["hintMode","","tb-help-popup-placement","right","trigger-style","letter-spacing:0.25px; font-size:12px;",1,"see-example",3,"tb-help-popup","tb-help-popup-style","trigger-text"],[4,"ngIf"],[1,"tb-form-panel","no-padding","no-border"],["popupHelpLink","rulenode/to_email_node_fields_templatization",3,"hintText"],[1,"flex"],["required","","matInput","","formControlName","toTemplate","cdkTextareaAutosize","","cdkAutosizeMinRows","1",1,"tb-enable-vertical-resize"],["matInput","","formControlName","ccTemplate","cdkTextareaAutosize","","cdkAutosizeMinRows","1",1,"tb-enable-vertical-resize"],["matInput","","formControlName","bccTemplate","cdkTextareaAutosize","","cdkAutosizeMinRows","1",1,"tb-enable-vertical-resize"],[1,"mat-block"],["required","","matInput","","formControlName","subjectTemplate","cdkTextareaAutosize","","cdkAutosizeMinRows","1",1,"tb-enable-vertical-resize"],["formControlName","mailBodyType"],[3,"value",4,"ngFor","ngForOf"],["class","mat-block",4,"ngIf"],["required","","matInput","","formControlName","bodyTemplate","cdkTextareaAutosize","","cdkAutosizeMinRows","2",1,"tb-enable-vertical-resize"],[3,"value"],["matListItemTitle",""],["matListItemMeta","",2,"color","inherit"],["required","","matInput","","formControlName","isHtmlTemplate"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"div",1)(2,"div",2),t.ɵɵtext(3,"tb.rulenode.email-sender"),t.ɵɵelementEnd(),t.ɵɵelementStart(4,"div",3)(5,"mat-form-field",4)(6,"mat-label",5),t.ɵɵtext(7,"tb.rulenode.from-template"),t.ɵɵelementEnd(),t.ɵɵelement(8,"input",6),t.ɵɵelementStart(9,"mat-hint",7),t.ɵɵtext(10),t.ɵɵpipe(11,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(12,"mat-hint",8)(13,"div",9),t.ɵɵelement(14,"div",10),t.ɵɵpipe(15,"translate"),t.ɵɵelementEnd()(),t.ɵɵtemplate(16,yp,3,3,"mat-error",11),t.ɵɵelementEnd()()(),t.ɵɵelementStart(17,"div",1)(18,"div",12)(19,"div",2),t.ɵɵtext(20,"tb.rulenode.recipients"),t.ɵɵelementEnd(),t.ɵɵelement(21,"tb-example-hint",13),t.ɵɵpipe(22,"translate"),t.ɵɵelementEnd(),t.ɵɵelementStart(23,"div",3)(24,"mat-form-field",14)(25,"mat-label",5),t.ɵɵtext(26,"tb.rulenode.to-template"),t.ɵɵelementEnd(),t.ɵɵelement(27,"textarea",15),t.ɵɵtemplate(28,bp,3,3,"mat-error",11),t.ɵɵelementEnd(),t.ɵɵelementStart(29,"mat-form-field",14)(30,"mat-label",5),t.ɵɵtext(31,"tb.rulenode.cc-template"),t.ɵɵelementEnd(),t.ɵɵelement(32,"textarea",16),t.ɵɵelementEnd(),t.ɵɵelementStart(33,"mat-form-field",14)(34,"mat-label",5),t.ɵɵtext(35,"tb.rulenode.bcc-template"),t.ɵɵelementEnd(),t.ɵɵelement(36,"textarea",17),t.ɵɵelementEnd()()(),t.ɵɵelementStart(37,"div",1)(38,"div",2),t.ɵɵtext(39,"tb.rulenode.message-subject-and-content"),t.ɵɵelementEnd(),t.ɵɵelement(40,"tb-example-hint",13),t.ɵɵpipe(41,"translate"),t.ɵɵelementStart(42,"section")(43,"mat-form-field",18)(44,"mat-label",5),t.ɵɵtext(45,"tb.rulenode.subject-template"),t.ɵɵelementEnd(),t.ɵɵelement(46,"textarea",19),t.ɵɵtemplate(47,vp,3,3,"mat-error",11),t.ɵɵelementEnd(),t.ɵɵelementStart(48,"mat-form-field",18)(49,"mat-label",5),t.ɵɵtext(50,"tb.rulenode.mail-body-type"),t.ɵɵelementEnd(),t.ɵɵelementStart(51,"mat-select",20)(52,"mat-select-trigger"),t.ɵɵtemplate(53,xp,3,3,"span",11),t.ɵɵelementEnd(),t.ɵɵtemplate(54,Cp,8,7,"mat-option",21),t.ɵɵelementEnd()(),t.ɵɵtemplate(55,Sp,6,0,"mat-form-field",22),t.ɵɵelementStart(56,"mat-form-field",18)(57,"mat-label",5),t.ɵɵtext(58,"tb.rulenode.body-template"),t.ɵɵelementEnd(),t.ɵɵelement(59,"textarea",23),t.ɵɵtemplate(60,Tp,3,3,"mat-error",11),t.ɵɵelementEnd()()()()),2&e&&(t.ɵɵproperty("formGroup",n.toEmailConfigForm),t.ɵɵadvance(10),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(11,14,"tb.rulenode.email-from-template-hint")," "),t.ɵɵadvance(4),t.ɵɵpropertyInterpolate("tb-help-popup","rulenode/to_email_node_fields_templatization"),t.ɵɵpropertyInterpolate("trigger-text",t.ɵɵpipeBind1(15,16,"tb.key-val.see-examples")),t.ɵɵproperty("tb-help-popup-style",t.ɵɵpureFunction0(22,hp)),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",n.toEmailConfigForm.get("fromTemplate").hasError("required")),t.ɵɵadvance(5),t.ɵɵproperty("hintText",t.ɵɵpipeBind1(22,18,"tb.rulenode.recipients-block-main-hint")),t.ɵɵadvance(7),t.ɵɵproperty("ngIf",n.toEmailConfigForm.get("toTemplate").hasError("required")),t.ɵɵadvance(12),t.ɵɵproperty("hintText",t.ɵɵpipeBind1(41,20,"tb.rulenode.kv-map-pattern-hint")),t.ɵɵadvance(7),t.ɵɵproperty("ngIf",n.toEmailConfigForm.get("subjectTemplate").hasError("required")),t.ɵɵadvance(6),t.ɵɵproperty("ngIf",n.toEmailConfigForm.get("mailBodyType").value),t.ɵɵadvance(),t.ɵɵproperty("ngForOf",n.mailBodyTypes),t.ɵɵadvance(),t.ɵɵproperty("ngIf","dynamic"===n.toEmailConfigForm.get("mailBodyType").value),t.ɵɵadvance(5),t.ɵɵproperty("ngIf",n.toEmailConfigForm.get("bodyTemplate").hasError("required")))},dependencies:t.ɵɵgetComponentDepsFactory(Ip),styles:["[_nghost-%COMP%] .input-bottom-double-hint[_ngcontent-%COMP%]{display:inline-flex}[_nghost-%COMP%] .input-bottom-double-hint[_ngcontent-%COMP%] .see-example[_ngcontent-%COMP%]{flex-shrink:0;padding-right:16px}[_nghost-%COMP%] textarea.tb-enable-vertical-resize[_ngcontent-%COMP%]{resize:vertical}"]})}}e("ToEmailConfigComponent",Ip);class Ep extends i{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.copyFrom=[],this.translation=Ut;for(const e of this.translation.keys())this.copyFrom.push({value:e,name:this.translate.instant(this.translation.get(e))})}onConfigurationSet(e){this.copyKeysConfigForm=this.fb.group({copyFrom:[e.copyFrom,[N.required]],keys:[e?e.keys:null,[N.required]]})}configForm(){return this.copyKeysConfigForm}prepareInputConfig(e){let t;return t=P(e?.fromMetadata)?e.copyFrom?Kt.METADATA:Kt.DATA:P(e?.copyFrom)?e.copyFrom:Kt.DATA,{keys:P(e?.keys)?e.keys:null,copyFrom:t}}static{this.ɵfac=function(e){return new(e||Ep)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.FormBuilder),t.ɵɵdirectiveInject(K.TranslateService))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Ep,selectors:[["tb-transformation-node-copy-keys-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:10,vars:17,consts:[[1,"tb-form-panel","no-padding","no-border",3,"formGroup"],["formControlName","copyFrom",3,"labelText","translation"],["required","","formControlName","keys",1,"mat-block",3,"label","placeholder","requiredText"],["matSuffix","","aria-hidden","false","aria-label","help-icon","color","primary",1,"help-icon","margin-8","cursor-pointer",3,"matTooltip"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0),t.ɵɵelement(1,"tb-msg-metadata-chip",1),t.ɵɵpipe(2,"translate"),t.ɵɵelementStart(3,"tb-string-items-list",2),t.ɵɵpipe(4,"translate"),t.ɵɵpipe(5,"translate"),t.ɵɵpipe(6,"translate"),t.ɵɵelementStart(7,"mat-icon",3),t.ɵɵpipe(8,"translate"),t.ɵɵtext(9," help "),t.ɵɵelementEnd()()()),2&e&&(t.ɵɵproperty("formGroup",n.copyKeysConfigForm),t.ɵɵadvance(),t.ɵɵproperty("labelText",t.ɵɵpipeBind1(2,7,"tb.key-val.copy-key-values-from"))("translation",n.translation),t.ɵɵadvance(2),t.ɵɵproperty("label",t.ɵɵpipeBind1(4,9,"tb.rulenode.keys"))("placeholder",t.ɵɵpipeBind1(5,11,"tb.rulenode.add-key"))("requiredText",t.ɵɵpipeBind1(6,13,"tb.key-val.at-least-one-key-error")),t.ɵɵadvance(4),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(8,15,"tb.rulenode.use-regular-expression-hint")))},dependencies:t.ɵɵgetComponentDepsFactory(Ep),encapsulation:2})}}function Fp(e,n){if(1&e&&(t.ɵɵelementStart(0,"tb-toggle-option",7),t.ɵɵtext(1),t.ɵɵelementEnd()),2&e){const e=n.$implicit;t.ɵɵproperty("value",e.value),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",e.name," ")}}e("CopyKeysConfigComponent",Ep);class qp extends i{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.renameIn=[],this.translation=zt;for(const e of this.translation.keys())this.renameIn.push({value:e,name:this.translate.instant(this.translation.get(e))})}configForm(){return this.renameKeysConfigForm}onConfigurationSet(e){this.renameKeysConfigForm=this.fb.group({renameIn:[e?e.renameIn:null,[N.required]],renameKeysMapping:[e?e.renameKeysMapping:null,[N.required]]})}prepareInputConfig(e){let t;return t=P(e?.fromMetadata)?e.fromMetadata?Kt.METADATA:Kt.DATA:P(e?.renameIn)?e?.renameIn:Kt.DATA,{renameKeysMapping:P(e?.renameKeysMapping)?e.renameKeysMapping:null,renameIn:t}}static{this.ɵfac=function(e){return new(e||qp)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.FormBuilder),t.ɵɵdirectiveInject(K.TranslateService))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:qp,selectors:[["tb-transformation-node-rename-keys-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:15,vars:24,consts:[[1,"tb-form-panel","stroked",3,"formGroup"],["translate","",1,"tb-form-panel-title"],[1,"fx-centered"],[1,"fetch-to-data-toggle"],["formControlName","renameIn","appearance","fill",1,"fetch-to-data-toggle"],[3,"value",4,"ngFor","ngForOf"],["required","","formControlName","renameKeysMapping","uniqueKeyValuePairValidator","",3,"labelText","requiredText","keyText","keyRequiredText","valText","valRequiredText"],[3,"value"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"div",1),t.ɵɵtext(2,"tb.rulenode.rename-keys-in"),t.ɵɵelementEnd(),t.ɵɵelementStart(3,"div",2)(4,"div",3)(5,"tb-toggle-select",4),t.ɵɵtemplate(6,Fp,2,2,"tb-toggle-option",5),t.ɵɵelementEnd()()(),t.ɵɵelement(7,"tb-kv-map-config",6),t.ɵɵpipe(8,"translate"),t.ɵɵpipe(9,"translate"),t.ɵɵpipe(10,"translate"),t.ɵɵpipe(11,"translate"),t.ɵɵpipe(12,"translate"),t.ɵɵpipe(13,"translate"),t.ɵɵpipe(14,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵproperty("formGroup",n.renameKeysConfigForm),t.ɵɵadvance(6),t.ɵɵproperty("ngForOf",n.renameIn),t.ɵɵadvance(),t.ɵɵpropertyInterpolate2("labelText","",t.ɵɵpipeBind1(8,10,n.translation.get(n.renameKeysConfigForm.get("renameIn").value))," ",t.ɵɵpipeBind1(9,12,"tb.rulenode.keys-mapping"),""),t.ɵɵpropertyInterpolate("requiredText",t.ɵɵpipeBind1(10,14,"tb.rulenode.attr-mapping-required")),t.ɵɵpropertyInterpolate("keyText",t.ɵɵpipeBind1(11,16,"tb.rulenode.current-key-name")),t.ɵɵpropertyInterpolate("keyRequiredText",t.ɵɵpipeBind1(12,18,"tb.rulenode.key-name-required")),t.ɵɵpropertyInterpolate("valText",t.ɵɵpipeBind1(13,20,"tb.rulenode.new-key-name")),t.ɵɵpropertyInterpolate("valRequiredText",t.ɵɵpipeBind1(14,22,"tb.rulenode.new-key-name-required")))},dependencies:t.ɵɵgetComponentDepsFactory(qp),styles:["[_nghost-%COMP%] .fetch-to-data-toggle[_ngcontent-%COMP%]{max-width:420px;width:100%}[_nghost-%COMP%] .fx-centered[_ngcontent-%COMP%]{display:flex;width:100%;justify-content:space-around}"]})}}function Ap(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(2,1,"tb.rulenode.json-path-expression-required")))}e("RenameKeysConfigComponent",qp);class kp extends i{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.jsonPathConfigForm}onConfigurationSet(e){this.jsonPathConfigForm=this.fb.group({jsonPath:[e?e.jsonPath:null,[N.required]]})}static{this.ɵfac=function(e){return new(e||kp)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.FormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:kp,selectors:[["tb-transformation-node-json-path-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:10,vars:8,consts:[[3,"formGroup"],["subscriptSizing","dynamic",1,"mat-block"],["matInput","","formControlName","jsonPath","required",""],[4,"ngIf"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"mat-form-field",1)(2,"mat-label"),t.ɵɵtext(3),t.ɵɵpipe(4,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(5,"input",2),t.ɵɵelementStart(6,"mat-hint"),t.ɵɵtext(7),t.ɵɵpipe(8,"translate"),t.ɵɵelementEnd(),t.ɵɵtemplate(9,Ap,3,3,"mat-error",3),t.ɵɵelementEnd()()),2&e&&(t.ɵɵproperty("formGroup",n.jsonPathConfigForm),t.ɵɵadvance(3),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(4,4,"tb.rulenode.json-path-expression")),t.ɵɵadvance(4),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(8,6,"tb.rulenode.json-path-expression-hint")),t.ɵɵadvance(2),t.ɵɵproperty("ngIf",n.jsonPathConfigForm.get("jsonPath").hasError("required")))},dependencies:t.ɵɵgetComponentDepsFactory(kp),encapsulation:2})}}e("NodeJsonPathConfigComponent",kp);class Np extends i{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.deleteFrom=[],this.translation=Ht;for(const e of this.translation.keys())this.deleteFrom.push({value:e,name:this.translate.instant(this.translation.get(e))})}onConfigurationSet(e){this.deleteKeysConfigForm=this.fb.group({deleteFrom:[e.deleteFrom,[N.required]],keys:[e?e.keys:null,[N.required]]})}prepareInputConfig(e){let t;return t=P(e?.fromMetadata)?e.fromMetadata?Kt.METADATA:Kt.DATA:P(e?.deleteFrom)?e?.deleteFrom:Kt.DATA,{keys:P(e?.keys)?e.keys:null,deleteFrom:t}}configForm(){return this.deleteKeysConfigForm}static{this.ɵfac=function(e){return new(e||Np)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.FormBuilder),t.ɵɵdirectiveInject(K.TranslateService))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Np,selectors:[["tb-transformation-node-delete-keys-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:10,vars:16,consts:[[1,"tb-form-panel","no-border","no-padding",3,"formGroup"],["formControlName","deleteFrom",3,"labelText"],["required","","formControlName","keys",1,"mat-block",3,"label","placeholder","requiredText"],["matSuffix","","aria-hidden","false","aria-label","help-icon","color","primary",1,"help-icon","margin-8","cursor-pointer",3,"matTooltip"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0),t.ɵɵelement(1,"tb-msg-metadata-chip",1),t.ɵɵpipe(2,"translate"),t.ɵɵelementStart(3,"tb-string-items-list",2),t.ɵɵpipe(4,"translate"),t.ɵɵpipe(5,"translate"),t.ɵɵpipe(6,"translate"),t.ɵɵelementStart(7,"mat-icon",3),t.ɵɵpipe(8,"translate"),t.ɵɵtext(9," help "),t.ɵɵelementEnd()()()),2&e&&(t.ɵɵproperty("formGroup",n.deleteKeysConfigForm),t.ɵɵadvance(),t.ɵɵproperty("labelText",t.ɵɵpipeBind1(2,6,"tb.key-val.delete-key-values-from")),t.ɵɵadvance(2),t.ɵɵproperty("label",t.ɵɵpipeBind1(4,8,"tb.rulenode.keys"))("placeholder",t.ɵɵpipeBind1(5,10,"tb.rulenode.add-key"))("requiredText",t.ɵɵpipeBind1(6,12,"tb.key-val.at-least-one-key-error")),t.ɵɵadvance(4),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(8,14,"tb.rulenode.use-regular-expression-delete-hint")))},dependencies:t.ɵɵgetComponentDepsFactory(Np),encapsulation:2})}}function wp(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.interval-required")," "))}function Mp(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.interval-min-error")," "))}function Bp(e,n){if(1&e&&(t.ɵɵelementStart(0,"tb-toggle-option",18),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e){const e=n.$implicit,r=t.ɵɵnextContext();t.ɵɵproperty("value",e),t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,2,r.deduplicationStrategiesTranslations.get(e))," ")}}function Vp(e,n){1&e&&(t.ɵɵelement(0,"tb-example-hint",19),t.ɵɵpipe(1,"translate")),2&e&&t.ɵɵproperty("hintText",t.ɵɵpipeBind1(1,1,"tb.rulenode.strategy-all-hint"))}function Op(e,n){1&e&&(t.ɵɵelement(0,"tb-example-hint",20),t.ɵɵpipe(1,"translate")),2&e&&t.ɵɵproperty("hintText",t.ɵɵpipeBind1(1,1,"tb.rulenode.strategy-first-hint"))}function Dp(e,n){1&e&&(t.ɵɵelement(0,"tb-example-hint",20),t.ɵɵpipe(1,"translate")),2&e&&t.ɵɵproperty("hintText",t.ɵɵpipeBind1(1,1,"tb.rulenode.strategy-last-hint"))}function Lp(e,n){1&e&&(t.ɵɵelementStart(0,"div"),t.ɵɵelement(1,"tb-output-message-type-autocomplete",21),t.ɵɵelementEnd())}function Pp(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.max-pending-msgs-required")," "))}function Rp(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.max-pending-msgs-max-error")," "))}function _p(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.max-pending-msgs-min-error")," "))}function jp(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.max-retries-required")," "))}function Gp(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.max-retries-max-error")," "))}function Kp(e,n){1&e&&(t.ɵɵelementStart(0,"mat-error"),t.ɵɵtext(1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵadvance(),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(2,1,"tb.rulenode.max-retries-min-error")," "))}e("DeleteKeysConfigComponent",Np);class Up extends i{constructor(e,t){super(e),this.store=e,this.fb=t,this.deduplicationStrategie=St,this.deduplicationStrategies=Object.keys(this.deduplicationStrategie),this.deduplicationStrategiesTranslations=Tt}configForm(){return this.deduplicationConfigForm}onConfigurationSet(e){this.deduplicationConfigForm=this.fb.group({interval:[P(e?.interval)?e.interval:null,[N.required,N.min(1)]],strategy:[P(e?.strategy)?e.strategy:null,[N.required]],outMsgType:[P(e?.outMsgType)?e.outMsgType:null,[N.required]],maxPendingMsgs:[P(e?.maxPendingMsgs)?e.maxPendingMsgs:null,[N.required,N.min(1),N.max(1e3)]],maxRetries:[P(e?.maxRetries)?e.maxRetries:null,[N.required,N.min(0),N.max(100)]]})}prepareInputConfig(e){return e||(e={}),e.outMsgType||(e.outMsgType="POST_TELEMETRY_REQUEST"),super.prepareInputConfig(e)}updateValidators(e){this.deduplicationConfigForm.get("strategy").value===this.deduplicationStrategie.ALL?this.deduplicationConfigForm.get("outMsgType").enable({emitEvent:!1}):this.deduplicationConfigForm.get("outMsgType").disable({emitEvent:!1}),this.deduplicationConfigForm.get("outMsgType").updateValueAndValidity({emitEvent:e})}validatorTriggers(){return["strategy"]}static{this.ɵfac=function(e){return new(e||Up)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.FormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Up,selectors:[["tb-action-node-msg-deduplication-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:49,vars:32,consts:[[3,"formGroup"],[1,"mat-block"],["type","number","required","","matInput","","formControlName","interval"],[4,"ngIf"],["matSuffix","","aria-hidden","false","aria-label","help-icon","color","primary",1,"help-icon","margin-8","cursor-pointer",3,"matTooltip"],[1,"tb-form-panel","no-padding","no-border"],[1,"tb-form-panel","stroked"],["translate","",1,"tb-form-panel-title","tb-required"],["formControlName","strategy","appearance","fill",1,"fetch-to-data-toggle"],[3,"value",4,"ngFor","ngForOf"],[3,"hintText",4,"ngIf"],["textAlign","'center'",3,"hintText",4,"ngIf"],[1,"tb-settings"],["translate",""],[1,"tb-form-row","no-border","no-padding","tb-standard-fields"],[1,"flex"],["type","number","required","","matInput","","formControlName","maxPendingMsgs"],["type","number","required","","matInput","","formControlName","maxRetries"],[3,"value"],[3,"hintText"],["textAlign","'center'",3,"hintText"],["required","","formControlName","outMsgType"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"mat-form-field",1)(2,"mat-label"),t.ɵɵtext(3),t.ɵɵpipe(4,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(5,"input",2),t.ɵɵtemplate(6,wp,3,3,"mat-error",3)(7,Mp,3,3,"mat-error",3),t.ɵɵelementStart(8,"mat-icon",4),t.ɵɵpipe(9,"translate"),t.ɵɵtext(10,"help"),t.ɵɵelementEnd()(),t.ɵɵelementStart(11,"div",5)(12,"div",6)(13,"div",7),t.ɵɵtext(14,"tb.rulenode.strategy"),t.ɵɵelementEnd(),t.ɵɵelementStart(15,"tb-toggle-select",8),t.ɵɵtemplate(16,Bp,3,4,"tb-toggle-option",9),t.ɵɵelementEnd(),t.ɵɵtemplate(17,Vp,2,3,"tb-example-hint",10)(18,Op,2,3,"tb-example-hint",11)(19,Dp,2,3,"tb-example-hint",11)(20,Lp,2,0,"div",3),t.ɵɵelementEnd(),t.ɵɵelementStart(21,"section",6)(22,"mat-expansion-panel",12)(23,"mat-expansion-panel-header")(24,"mat-panel-title",13),t.ɵɵtext(25,"tb.rulenode.advanced-settings"),t.ɵɵelementEnd()(),t.ɵɵelementStart(26,"div",14)(27,"mat-form-field",15)(28,"mat-label"),t.ɵɵtext(29),t.ɵɵpipe(30,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(31,"input",16),t.ɵɵtemplate(32,Pp,3,3,"mat-error",3)(33,Rp,3,3,"mat-error",3)(34,_p,3,3,"mat-error",3),t.ɵɵelementStart(35,"mat-icon",4),t.ɵɵpipe(36,"translate"),t.ɵɵtext(37,"help"),t.ɵɵelementEnd()(),t.ɵɵelementStart(38,"mat-form-field",15)(39,"mat-label"),t.ɵɵtext(40),t.ɵɵpipe(41,"translate"),t.ɵɵelementEnd(),t.ɵɵelement(42,"input",17),t.ɵɵtemplate(43,jp,3,3,"mat-error",3)(44,Gp,3,3,"mat-error",3)(45,Kp,3,3,"mat-error",3),t.ɵɵelementStart(46,"mat-icon",4),t.ɵɵpipe(47,"translate"),t.ɵɵtext(48,"help"),t.ɵɵelementEnd()()()()()()()),2&e&&(t.ɵɵproperty("formGroup",n.deduplicationConfigForm),t.ɵɵadvance(3),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(4,20,"tb.rulenode.interval")),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",n.deduplicationConfigForm.get("interval").hasError("required")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.deduplicationConfigForm.get("interval").hasError("min")),t.ɵɵadvance(),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(9,22,"tb.rulenode.interval-hint")),t.ɵɵadvance(8),t.ɵɵproperty("ngForOf",n.deduplicationStrategies),t.ɵɵadvance(),t.ɵɵproperty("ngIf","ALL"===n.deduplicationConfigForm.get("strategy").value),t.ɵɵadvance(),t.ɵɵproperty("ngIf","FIRST"===n.deduplicationConfigForm.get("strategy").value),t.ɵɵadvance(),t.ɵɵproperty("ngIf","LAST"===n.deduplicationConfigForm.get("strategy").value),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.deduplicationConfigForm.get("strategy").value===n.deduplicationStrategie.ALL),t.ɵɵadvance(9),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(30,24,"tb.rulenode.max-pending-msgs")),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",n.deduplicationConfigForm.get("maxPendingMsgs").hasError("required")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.deduplicationConfigForm.get("maxPendingMsgs").hasError("max")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.deduplicationConfigForm.get("maxPendingMsgs").hasError("min")),t.ɵɵadvance(),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(36,26,"tb.rulenode.max-pending-msgs-hint")),t.ɵɵadvance(5),t.ɵɵtextInterpolate(t.ɵɵpipeBind1(41,28,"tb.rulenode.max-retries")),t.ɵɵadvance(3),t.ɵɵproperty("ngIf",n.deduplicationConfigForm.get("maxRetries").hasError("required")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.deduplicationConfigForm.get("maxRetries").hasError("max")),t.ɵɵadvance(),t.ɵɵproperty("ngIf",n.deduplicationConfigForm.get("maxRetries").hasError("min")),t.ɵɵadvance(),t.ɵɵpropertyInterpolate("matTooltip",t.ɵɵpipeBind1(47,30,"tb.rulenode.max-retries-hint")))},dependencies:t.ɵɵgetComponentDepsFactory(Up),encapsulation:2})}}e("DeduplicationConfigComponent",Up);class Hp{static{this.ɵfac=function(e){return new(e||Hp)}}static{this.ɵmod=t.ɵɵdefineNgModule({type:Hp})}static{this.ɵinj=t.ɵɵdefineInjector({imports:[$,S,xi,sp,gp,Ip,Ep,qp,kp,Np,Up]})}}e("RulenodeCoreConfigTransformModule",Hp),("undefined"==typeof ngJitMode||ngJitMode)&&t.ɵɵsetNgModuleScope(Hp,{declarations:[sp,gp,Ip,Ep,qp,kp,Np,Up],imports:[$,S,xi],exports:[sp,gp,Ip,Ep,qp,kp,Np,Up]});const zp=e=>[e];class $p extends i{constructor(e,t){super(e),this.store=e,this.fb=t,this.entityType=u}configForm(){return this.ruleChainInputConfigForm}onConfigurationSet(e){this.ruleChainInputConfigForm=this.fb.group({forwardMsgToDefaultRuleChain:[!!e&&e?.forwardMsgToDefaultRuleChain,[]],ruleChainId:[e?e.ruleChainId:null,[N.required]]})}static{this.ɵfac=function(e){return new(e||$p)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:$p,selectors:[["tb-flow-node-rule-chain-input-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:8,vars:12,consts:[[1,"flex","flex-col",3,"formGroup"],[1,"tb-form-panel","no-padding","no-border"],[1,"tb-form-row","no-border",3,"tb-hint-tooltip-icon"],["formControlName","forwardMsgToDefaultRuleChain",1,"mat-slide"],["required","","formControlName","ruleChainId",3,"excludeEntityIds","entityType","entitySubtype"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0)(1,"div",1)(2,"div",2),t.ɵɵpipe(3,"translate"),t.ɵɵelementStart(4,"mat-slide-toggle",3),t.ɵɵtext(5),t.ɵɵpipe(6,"translate"),t.ɵɵelementEnd()(),t.ɵɵelement(7,"tb-entity-autocomplete",4),t.ɵɵelementEnd()()),2&e&&(t.ɵɵproperty("formGroup",n.ruleChainInputConfigForm),t.ɵɵadvance(2),t.ɵɵpropertyInterpolate("tb-hint-tooltip-icon",t.ɵɵpipeBind1(3,6,"tb.rulenode.forward-msg-default-rule-chain-tooltip")),t.ɵɵadvance(3),t.ɵɵtextInterpolate1(" ",t.ɵɵpipeBind1(6,8,"tb.rulenode.forward-msg-default-rule-chain")," "),t.ɵɵadvance(2),t.ɵɵproperty("excludeEntityIds",t.ɵɵpureFunction1(10,zp,n.ruleChainId))("entityType",n.entityType.RULE_CHAIN)("entitySubtype",n.ruleChainType))},dependencies:t.ɵɵgetComponentDepsFactory($p),encapsulation:2})}}e("RuleChainInputComponent",$p);class Qp extends i{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.ruleChainOutputConfigForm}onConfigurationSet(e){this.ruleChainOutputConfigForm=this.fb.group({})}static{this.ɵfac=function(e){return new(e||Qp)(t.ɵɵdirectiveInject(A.Store),t.ɵɵdirectiveInject(k.UntypedFormBuilder))}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Qp,selectors:[["tb-flow-node-rule-chain-output-config"]],features:[t.ɵɵInheritDefinitionFeature],decls:3,vars:4,consts:[[1,"flex","flex-col",3,"formGroup"],[3,"innerHTML"]],template:function(e,n){1&e&&(t.ɵɵelementStart(0,"section",0),t.ɵɵelement(1,"div",1),t.ɵɵpipe(2,"translate"),t.ɵɵelementEnd()),2&e&&(t.ɵɵproperty("formGroup",n.ruleChainOutputConfigForm),t.ɵɵadvance(),t.ɵɵpropertyInterpolate("innerHTML",t.ɵɵpipeBind1(2,2,"tb.rulenode.output-node-name-hint"),t.ɵɵsanitizeHtml))},dependencies:t.ɵɵgetComponentDepsFactory(Qp),encapsulation:2})}}e("RuleChainOutputComponent",Qp);class Jp{static{this.ɵfac=function(e){return new(e||Jp)}}static{this.ɵmod=t.ɵɵdefineNgModule({type:Jp})}static{this.ɵinj=t.ɵɵdefineInjector({imports:[$,S,xi,$p,Qp]})}}e("RuleNodeCoreConfigFlowModule",Jp),("undefined"==typeof ngJitMode||ngJitMode)&&t.ɵɵsetNgModuleScope(Jp,{declarations:[$p,Qp],imports:[$,S,xi],exports:[$p,Qp]});class Yp{constructor(){}static{this.ɵfac=function(e){return new(e||Yp)}}static{this.ɵcmp=t.ɵɵdefineComponent({type:Yp,selectors:[["tb-lib-styles-entry"]],standalone:!0,features:[t.ɵɵStandaloneFeature],decls:0,vars:0,template:function(e,t){},styles:['.tb-default tb-rule-node-config .margin-8{margin:8px}.tb-default tb-rule-node-config .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}.tb-default tb-rule-node-config .tb-required:after{content:"*";font-size:16px;color:#000000de}.tb-default tb-rule-node-config .help-icon{color:#000;opacity:.38;padding:unset}.tb-default tb-rule-node-config .help-icon:hover{color:#305680;opacity:unset}.tb-default tb-rule-node-config .same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.tb-default tb-rule-node-config .same-width-component-row{gap:8px}}.tb-default tb-rule-node-config .same-width-component-row>*{flex:1}.tb-default .gap-0{gap:0px}.tb-default .gap-5\\.5{gap:1.375rem}@media (max-width: 599px){.tb-default .xs\\:max-h-full{max-height:100%}}@media (min-width: 960px) and (max-width: 1279px){.tb-default .md\\:max-h-full{max-height:100%}}@media (max-width: 959px){.tb-default .lt-md\\:gap-4{gap:1rem}}@media (min-width: 960px){.tb-default .gt-sm\\:max-w-10{max-width:2.5rem}.tb-default .gt-sm\\:max-w-10\\%{max-width:10%}.tb-default .gt-sm\\:gap-4{gap:1rem}.tb-default .gt-sm\\:gap-5\\.5{gap:1.375rem}}\n'],encapsulation:2})}}const Wp=(e,t)=>{const n=e[a];if(n.styles?.length){const e=n.styles[0];let r=document.getElementById(t);if(!r){r=document.createElement("style"),r.id=t;(document.head||document.getElementsByTagName("head")[0]).appendChild(r)}r.innerHTML=e}};class Xp{constructor(e){!function(e){e.setTranslation("en_US",{tb:{rulenode:{id:"Id","additional-info":"Additional Info","advanced-settings":"Advanced settings","create-entity-if-not-exists":"Create new entity if it doesn't exist","create-entity-if-not-exists-hint":"If enabled, a new entity with specified parameters will be created unless it already exists. Existing entities will be used as is for relation.","select-device-connectivity-event":"Select device connectivity event","entity-name-pattern":"Name pattern","device-name-pattern":"Device name","asset-name-pattern":"Asset name","entity-view-name-pattern":"Entity view name","customer-title-pattern":"Customer title","dashboard-name-pattern":"Dashboard title","user-name-pattern":"User email","edge-name-pattern":"Edge name","entity-name-pattern-required":"Name pattern is required","entity-name-pattern-hint":"Name pattern field support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","copy-message-type":"Copy message type","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","message-type-value":"Message type value","message-type-value-required":"Message type value is required","message-type-value-max-length":"Message type value should be less than 256","output-message-type":"Output message type","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer title","customer-name-pattern-required":"Customer title is required","customer-name-pattern-hint":"Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","create-customer-if-not-exists":"Create new customer if it doesn't exist","unassign-from-customer":"Unassign from specific customer if originator is dashboard","unassign-from-customer-tooltip":"Only dashboards can be assigned to multiple customers at once. \nIf the message originator is a dashboard, you need to explicitly specify the customer's title to unassign from.","customer-cache-expiration":"Customers cache expiration time (sec)","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","interval-start":"Interval start","interval-end":"Interval end","time-unit":"Time unit","fetch-mode":"Fetch mode","order-by-timestamp":"Order by timestamp",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. If you want to fetch a single entry, select fetch mode 'First' or 'Last'.","limit-required":"Limit is required.","limit-range":"Limit should be in a range from 2 to 1000.","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Allowing range from 1 to 2147483647.","start-interval-value-required":"Interval start is required.","end-interval-value-required":"Interval end is required.",filter:"Filter",switch:"Switch","math-templatization-tooltip":"This field support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","add-message-type":"Add message type","select-message-types-required":"At least one message type should be selected.","select-message-types":"Select message types","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one.","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","attributes-keys":"Attributes keys","attributes-keys-required":"Attributes keys are required","attributes-scope":"Attributes scope","attributes-scope-value":"Attributes scope value","attributes-scope-value-copy":"Copy attributes scope value","attributes-scope-hint":"Use the 'scope' metadata key to dynamically set the attribute scope per message. If provided, this overrides the scope set in the configuration.","notify-device":"Force notification to the device","send-attributes-updated-notification":"Send attributes updated notification","send-attributes-updated-notification-hint":"Send notification about updated attributes as a separate message to the rule engine queue.","send-attributes-deleted-notification":"Send attributes deleted notification","send-attributes-deleted-notification-hint":"Send notification about deleted attributes as a separate message to the rule engine queue.","update-attributes-only-on-value-change":"Save attributes only if the value changes","update-attributes-only-on-value-change-hint":"Updates the attributes on every incoming message disregarding if their value has changed. Increases API usage and reduces performance.","update-attributes-only-on-value-change-hint-enabled":"Updates the attributes only if their value has changed. If the value is not changed, no update to the attribute timestamp nor attribute change notification will be sent.","fetch-credentials-to-metadata":"Fetch credentials to metadata","notify-device-on-update-hint":"If enabled, force notification to the device about shared attributes update. If disabled, the notification behavior is controlled by the 'notifyDevice' parameter from the incoming message metadata. To turn off the notification, the message metadata must contain the 'notifyDevice' parameter set to 'false'. Any other case will trigger the notification to the device.","notify-device-on-delete-hint":"If enabled, force notification to the device about shared attributes removal. If disabled, the notification behavior is controlled by the 'notifyDevice' parameter from the incoming message metadata. To turn on the notification, the message metadata must contain the 'notifyDevice' parameter set to 'true'. In any other case, the notification will not be triggered to the device.","latest-timeseries":"Latest time series data keys","timeseries-keys":"Time series keys","timeseries-keys-required":"At least one time series key should be selected.","add-timeseries-key":"Add time series key","add-message-field":"Add message field","relation-search-parameters":"Relation search parameters","relation-parameters":"Relation parameters","add-metadata-field":"Add metadata field","data-keys":"Message field names","copy-from":"Copy from","data-to-metadata":"Data to metadata","metadata-to-data":"Metadata to data","use-regular-expression-hint":"Use regular expression to copy keys by pattern.\n\nTips & tricks:\nPress 'Enter' to complete field name input.\nPress 'Backspace' to delete field name. Multiple field names supported.",interval:"Interval","interval-required":"Interval is required","interval-hint":"Deduplication interval in seconds.","interval-min-error":"Min allowed value is 1","max-pending-msgs":"Max pending messages","max-pending-msgs-hint":"Maximum number of messages that are stored in memory for each unique deduplication id.","max-pending-msgs-required":"Max pending messages is required","max-pending-msgs-max-error":"Max allowed value is 1000","max-pending-msgs-min-error":"Min allowed value is 1","max-retries":"Max retries","max-retries-required":"Max retries is required","max-retries-hint":"Maximum number of retries to push the deduplicated messages into the queue. 10 seconds delay is used between retries","max-retries-max-error":"Max allowed value is 100","max-retries-min-error":"Min allowed value is 0",strategy:"Strategy","strategy-required":"Strategy is required","strategy-all-hint":"Return all messages that arrived during deduplication period as a single JSON array message. Where each element represents object with msg and metadata inner properties.","strategy-first-hint":"Return first message that arrived during deduplication period.","strategy-last-hint":"Return last message that arrived during deduplication period.",first:"First",last:"Last",all:"All","output-msg-type-hint":"The message type of the deduplication result.","queue-name-hint":"The queue name where the deduplication result will be published.",keys:"Keys","keys-required":"Keys are required","rename-keys-in":"Rename keys in",data:"Data",message:"Message",metadata:"Metadata","current-key-name":"Current key name","key-name-required":"Key name is required","new-key-name":"New key name","new-key-name-required":"New key name is required","metadata-keys":"Metadata field names","json-path-expression":"JSON path expression","json-path-expression-required":"JSON path expression is required","json-path-expression-hint":"JSONPath specifies a path to an element or a set of elements in a JSON structure. '$' represents the root object or array.","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","max-relation-level-error":"Value should be greater than 0 or unspecified.","max-relation-level-invalid":"Value should be an integer.","relation-type":"Relation type","relation-type-pattern":"Relation type pattern","relation-type-pattern-required":"Relation type pattern is required","relation-types-list":"Relation types to propagate","relation-types-list-hint":"If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","add-telemetry-key":"Add telemetry key","delete-from":"Delete from","use-regular-expression-delete-hint":"Use regular expression to delete keys by pattern.\n\nTips & tricks:\nPress 'Enter' to complete field name input.\nPress 'Backspace' to delete field name.\nMultiple field names supported.","fetch-into":"Fetch into","attr-mapping":"Attributes mapping:","source-attribute":"Source attribute key","source-attribute-required":"Source attribute key is required.","source-telemetry":"Source telemetry key","source-telemetry-required":"Source telemetry key is required.","target-key":"Target key","target-key-required":"Target key is required.","attr-mapping-required":"At least one mapping entry should be specified.","fields-mapping":"Fields mapping","fields-mapping-hint":"If the message field is set to $entityId, the message originator's id will be saved to the specified table column.","relations-query-config-direction-suffix":"originator","profile-name":"Profile name","fetch-circle-parameter-info-from-metadata-hint":'Metadata field \'{{perimeterKeyName}}\' should be defined in next format: {"latitude":48.196, "longitude":24.6532, "radius":100.0, "radiusUnit":"METER"}',"fetch-poligon-parameter-info-from-metadata-hint":"Metadata field '{{perimeterKeyName}}' should be defined in next format: [[48.19736,24.65235],[48.19800,24.65060],...,[48.19849,24.65420]]","short-templatization-tooltip":"Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","fields-mapping-required":"At least one field mapping should be specified.","at-least-one-field-required":"At least one input field must have a value(s) provided.","originator-fields-sv-map-hint":"Target key fields support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","sv-map-hint":"Only target key fields support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","new-originator":"New originator","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related entity","originator-alarm-originator":"Alarm Originator","originator-entity":"Entity by name pattern","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","default-ttl-hint":"Rule node will fetch Time-to-Live (TTL) value from the message metadata. If no value is present, it defaults to the TTL specified in the configuration. If the value is set to 0, the TTL from the tenant profile configuration will be applied.","default-ttl-zero-hint":"TTL will not be applied if its value is set to 0.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","generation-parameters":"Generation parameters","message-count":"Generated messages limit (0 - unlimited)","message-count-required":"Generated messages limit is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","generation-frequency-seconds":"Generation frequency in seconds","generation-frequency-required":"Generation frequency is required.","min-generation-frequency-message":"Only 1 second minimum is allowed.","script-lang-tbel":"TBEL","script-lang-js":"JS","use-metadata-period-in-seconds-patterns":"Use period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata or data assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","current-rule-node":"Current Rule Node","current-tenant":"Current Tenant","generator-function":"Generator function","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","select-entity-types":"Select entity types","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required","alarm-severity-pattern":"Alarm severity pattern","alarm-status-filter":"Alarm status filter","alarm-status-list-empty":"Alarm status list is empty","no-alarm-status-matching":"No alarm status matching were found.",propagate:"Propagate alarm to related entities","propagate-to-owner":"Propagate alarm to entity owner (Customer or Tenant)","propagate-to-tenant":"Propagate alarm to Tenant",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From","from-template-required":"From is required","message-to-metadata":"Message to metadata","metadata-to-message":"Metadata to message","from-message":"From message","from-metadata":"From metadata","to-template":"To","to-template-required":"To Template is required","mail-address-list-template-hint":'Comma separated address list, use ${metadataKey} for value from metadata, $[messageKey] for value from message body',"cc-template":"Cc","bcc-template":"Bcc","subject-template":"Subject","subject-template-required":"Subject Template is required","body-template":"Body","body-template-required":"Body Template is required","dynamic-mail-body-type":"Dynamic mail body type","mail-body-type":"Mail body type","body-type-template":"Body type template","reply-routing-configuration":"Reply Routing Configuration","rpc-reply-routing-configuration-hint":"These configuration parameters specify the metadata key names used to identify the service, session, and request for sending a reply back.","reply-routing-configuration-hint":"These configuration parameters specify the metadata key names used to identify the service and request for sending a reply back.","request-id-metadata-attribute":"Request Id","service-id-metadata-attribute":"Service Id","session-id-metadata-attribute":"Session Id","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory","ignore-request-body":"Without request body","parse-to-plain-text":"Parse to plain text","parse-to-plain-text-hint":'If selected, request body message payload will be transformed from JSON string to plain text, e.g. msg = "Hello,\\t\\"world\\"" will be parsed to Hello, "world"',"read-timeout":"Read timeout in millis","read-timeout-hint":"The value of 0 means an infinite timeout","max-parallel-requests-count":"Max number of parallel requests","max-parallel-requests-count-hint":"The value of 0 specifies no limit in parallel processing","max-response-size":"Max response size (in KB)","max-response-size-hint":"The maximum amount of memory allocated for buffering data when decoding or encoding HTTP messages, such as JSON or XML payloads",headers:"Headers","headers-hint":'Use ${metadataKey} for value from metadata, $[messageKey] for value from message body in header/value fields',header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","key-pattern":"Key pattern","key-pattern-hint":"Optional. If a valid partition number is specified, it will be used when sending the record. If no partition is specified, the key will be used instead. If neither is specified, a partition will be assigned in a round-robin fashion.","topic-pattern-required":"Topic pattern is required",topic:"Topic","topic-required":"Topic is required","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.","memory-buffer-size-range":"Memory buffer size must be between 0 and {{max}} KB",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","gcp-project-id":"GCP project ID","gcp-project-id-required":"GCP project ID is required","gcp-service-account-key":"GCP service account key file","gcp-service-account-key-required":"GCP service account key file is required","pubsub-topic-name":"Topic name","pubsub-topic-name-required":"Topic name is required","message-attributes":"Message attributes","message-attributes-hint":'Use ${metadataKey} for value from metadata, $[messageKey] for value from message body in name/value fields',"connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","client-id-hint":'Optional. Leave empty for auto-generated Client ID. Be careful when specifying the Client ID. Majority of the MQTT brokers will not allow multiple connections with the same Client ID. To connect to such brokers, your mqtt Client ID must be unique. When platform is running in a micro-services mode, the copy of rule node is launched in each micro-service. This will automatically lead to multiple mqtt clients with the same ID and may cause failures of the rule node. To avoid such failures enable "Add Service ID as suffix to Client ID" option below.',"append-client-id-suffix":"Add Service ID as suffix to Client ID","client-id-suffix-hint":'Optional. Applied when "Client ID" specified explicitly. If selected then Service ID will be added to Client ID as a suffix. Helps to avoid failures when platform is running in a micro-services mode.',"device-id":"Device ID","device-id-required":"Device ID is required.","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","credentials-pem-hint":"At least Server CA certificate file or a pair of Client certificate and Client private key files are required","credentials-sas":"Shared Access Signature","sas-key":"SAS Key","sas-key-required":"SAS Key is required.",hostname:"Hostname","hostname-required":"Hostname is required.","azure-ca-cert":"CA certificate file","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"Server CA certificate file","private-key":"Client private key file",cert:"Client certificate file","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","use-metadata-dynamic-interval":"Use dynamic interval","metadata-dynamic-interval-hint":"Interval start and end input fields support templatization. Note that the substituted template value should be set in milliseconds. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata or data assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","overwrite-alarm-details":"Overwrite alarm details","use-alarm-severity-pattern":"Use alarm severity pattern","check-all-keys":"Check that all specified fields are present","check-all-keys-hint":"If selected, checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity","check-relation-to-specific-entity-tooltip":"If enabled, checks the presence of relation with a specific entity otherwise, checks the presence of relation with any entity. In both cases, relation lookup is based on configured direction and type.","check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-with-specific-entity":"Delete relation with specific entity","delete-relation-with-specific-entity-hint":"If enabled, will delete the relation with just one specific entity. Otherwise, the relation will be removed with all matching entities.","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","remove-current-relations":"Remove current relations","remove-current-relations-hint":"Removes current relations from the originator of the incoming message based on direction and type.","change-originator-to-related-entity":"Change originator to related entity","change-originator-to-related-entity-hint":"Used to process submitted message as a message from another entity.","start-interval":"Interval start","end-interval":"Interval end","start-interval-required":"Interval start is required.","end-interval-required":"Interval end is required.","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","tls-version":"TLS version","enable-proxy":"Enable proxy","use-system-proxy-properties":"Use system proxy properties","proxy-host":"Proxy host","proxy-host-required":"Proxy host is required.","proxy-port":"Proxy port","proxy-port-required":"Proxy port is required.","proxy-port-range":"Proxy port should be in a range from 1 to 65535.","proxy-user":"Proxy user","proxy-password":"Proxy password","proxy-scheme":"Proxy scheme","numbers-to-template":"Phone Numbers To Template","numbers-to-template-required":"Phone Numbers To Template is required","numbers-to-template-hint":'Comma separated Phone Numbers, use ${metadataKey} for value from metadata, $[messageKey] for value from message body',"sms-message-template":"SMS message Template","sms-message-template-required":"SMS message Template is required","use-system-sms-settings":"Use system SMS provider settings","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","int-range":"Value must not exceed the maximum integer limit (2147483648)","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output time series key prefix","output-timeseries-key-prefix-required":"Output time series key prefix required.","separator-hint":'Press "Enter" to complete field input.',"select-details":"Select details","entity-details-id":"Id","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","entity-details-city":"City","entity-details-zip":"Zip","entity-details-address":"Address","entity-details-address2":"Address2","entity-details-additional_info":"Additional Info","entity-details-phone":"Phone","entity-details-email":"Email","email-sender":"Email sender","fields-to-check":"Fields to check","add-detail":"Add detail","check-all-keys-tooltip":"If enabled, checks the presence of all fields listed in the message and metadata field names within the incoming message and its metadata.","fields-to-check-hint":'Press "Enter" to complete field name input. Multiple field names supported.',"entity-details-list-empty":"At least one detail should be selected.","alarm-status":"Alarm status","alarm-required":"At least one alarm status should be selected.","no-entity-details-matching":"No entity details matching were found.","custom-table-name":"Custom table name","custom-table-name-required":"Table Name is required","custom-table-hint":"The table must be created in your Cassandra cluster and its name must start with the prefix 'cs_tb_' to avoid the data insertion to the common TB tables. Enter the table name here without the 'cs_tb_' prefix.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-field-name":"Latitude field name","longitude-field-name":"Longitude field name","latitude-field-name-required":"Latitude field name is required.","longitude-field-name-required":"Longitude field name is required.","fetch-perimeter-info-from-metadata":"Fetch perimeter information from metadata","fetch-perimeter-info-from-metadata-tooltip":"If perimeter type is set to 'Polygon' the value of metadata field '{{perimeterKeyName}}' will be set as perimeter definition without additional parsing of the value. Otherwise, if perimeter type is set to 'Circle' the value of '{{perimeterKeyName}}' metadata field will be parsed to extract 'latitude', 'longitude', 'radius', 'radiusUnit' fields that uses for circle perimeter definition.","perimeter-key-name":"Perimeter key name","perimeter-key-name-hint":"Metadata field name that includes perimeter information.","perimeter-key-name-required":"Perimeter key name is required.","perimeter-circle":"Circle","perimeter-polygon":"Polygon","perimeter-type":"Perimeter type","circle-center-latitude":"Center latitude","circle-center-latitude-required":"Center latitude is required.","circle-center-longitude":"Center longitude","circle-center-longitude-required":"Center longitude is required.","range-unit-meter":"Meter","range-unit-kilometer":"Kilometer","range-unit-foot":"Foot","range-unit-mile":"Mile","range-unit-nautical-mile":"Nautical mile","range-units":"Range units","range-units-required":"Range units is required.",range:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].","min-inside-duration":"Minimal inside duration","min-inside-duration-value-required":"Minimal inside duration is required","min-inside-duration-time-unit":"Minimal inside duration time unit","min-outside-duration":"Minimal outside duration","min-outside-duration-value-required":"Minimal outside duration is required","min-outside-duration-time-unit":"Minimal outside duration time unit","tell-failure-if-absent":"Tell Failure","tell-failure-if-absent-hint":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"get-latest-value-with-ts":"Fetch timestamp for the latest telemetry values","get-latest-value-with-ts-hint":'If selected, the latest telemetry values will also include timestamp, e.g: "temp": "{"ts":1574329385897, "value":42}"',"ignore-null-strings":"Ignore null strings","ignore-null-strings-hint":"If selected rule node will ignore entity fields with empty value.","add-metadata-key-values-as-kafka-headers":"Add Message metadata key-value pairs to Kafka record headers","add-metadata-key-values-as-kafka-headers-hint":"If selected, key-value pairs from message metadata will be added to the outgoing records headers as byte arrays with predefined charset encoding.","charset-encoding":"Charset encoding","charset-encoding-required":"Charset encoding is required.","charset-us-ascii":"US-ASCII","charset-iso-8859-1":"ISO-8859-1","charset-utf-8":"UTF-8","charset-utf-16be":"UTF-16BE","charset-utf-16le":"UTF-16LE","charset-utf-16":"UTF-16","select-queue-hint":"The queue name can be selected from a drop-down list or add a custom name.","device-profile-node-hint":"Useful if you have duration or repeating conditions to ensure continuity of alarm state evaluation.","persist-alarm-rules":"Persist state of alarm rules","persist-alarm-rules-hint":"If enabled, the rule node will store the state of processing to the database.","fetch-alarm-rules":"Fetch state of alarm rules","fetch-alarm-rules-hint":"If enabled, the rule node will restore the state of processing on initialization and ensure that alarms are raised even after server restarts. Otherwise, the state will be restored when the first message from the device arrives.","input-value-key":"Input value key","input-value-key-required":"Input value key is required.","output-value-key":"Output value key","output-value-key-required":"Output value key is required.","number-of-digits-after-floating-point":"Number of digits after floating point","number-of-digits-after-floating-point-range":"Number of digits after floating point should be in a range from 0 to 15.","failure-if-delta-negative":"Tell Failure if delta is negative","failure-if-delta-negative-tooltip":"Rule node forces failure of message processing if delta value is negative.","use-caching":"Use caching","use-caching-tooltip":'Rule node will cache the value of "{{inputValueKey}}" that arrives from the incoming message to improve performance. Note that the cache will not be updated if you modify the "{{inputValueKey}}" value elsewhere.',"add-time-difference-between-readings":'Add the time difference between "{{inputValueKey}}" readings',"add-time-difference-between-readings-tooltip":'If enabled, the rule node will add the "{{periodValueKey}}" to the outbound message.',"period-value-key":"Period value key","period-value-key-required":"Period value key is required.","general-pattern-hint":"Use ${metadataKey} for value from metadata, $[messageKey] for value from message body.","alarm-severity-pattern-hint":'Use ${metadataKey} for value from metadata, $[messageKey] for value from message body. Alarm severity should be system (CRITICAL, MAJOR etc.)',"output-node-name-hint":"The rule node name corresponds to the relation type of the output message, and it is used to forward messages to other rule nodes in the caller rule chain.","skip-latest-persistence":"Skip latest persistence","skip-latest-persistence-hint":"Rule node will not update values for incoming keys for the latest time series data. Useful for highly loaded use-cases to decrease the pressure on the DB.","use-server-ts":"Use server ts","use-server-ts-hint":"Rule node will use the timestamp of message processing instead of the timestamp from the message. Useful for all sorts of sequential processing if you merge messages from multiple sources (devices, assets, etc).","kv-map-pattern-hint":"All input fields support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","kv-map-single-pattern-hint":"Input field support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","shared-scope":"Shared scope","server-scope":"Server scope","client-scope":"Client scope","attribute-type":"Attribute","constant-type":"Constant","time-series-type":"Time series","message-body-type":"Message","message-metadata-type":"Metadata","argument-tile":"Arguments","no-arguments-prompt":"No arguments configured","result-title":"Result","functions-field-input":"Functions","no-option-found":"No option found","argument-source-field-input":"Source","argument-source-field-input-required":"Argument source is required.","argument-key-field-input":"Key","argument-key-field-input-required":"Argument key is required.","constant-value-field-input":"Constant value","constant-value-field-input-required":"Constant value is required.","attribute-scope-field-input":"Attribute scope","attribute-scope-field-input-required":"Attribute scope os required.","default-value-field-input":"Default value","type-field-input":"Type","type-field-input-required":"Type is required.","key-field-input":"Key","add-entity-type":"Add entity type","add-device-profile":"Add device profile","key-field-input-required":"Key is required.","number-floating-point-field-input":"Number of digits after floating point","number-floating-point-field-input-hint":"Use 0 to convert result to integer","add-to-message-field-input":"Add to message","add-to-metadata-field-input":"Add to metadata","custom-expression-field-input":"Mathematical Expression","custom-expression-field-input-required":"Mathematical expression is required","custom-expression-field-input-hint":"Specify a mathematical expression to evaluate. Default expression demonstrates how to transform Fahrenheit to Celsius","retained-message":"Retained","attributes-mapping":"Attributes mapping","latest-telemetry-mapping":"Latest telemetry mapping","add-mapped-attribute-to":"Add mapped attributes to","add-mapped-latest-telemetry-to":"Add mapped latest telemetry to","add-mapped-fields-to":"Add mapped fields to","add-selected-details-to":"Add selected details to","clear-selected-types":"Clear selected types","clear-selected-details":"Clear selected details","clear-selected-fields":"Clear selected fields","clear-selected-keys":"Clear selected keys","geofence-configuration":"Geofence configuration","coordinate-field-names":"Coordinate field names","coordinate-field-hint":"Rule node tries to retrieve the specified fields from the message. If they are not present, it will look them up in the metadata.","presence-monitoring-strategy":"Presence monitoring strategy","presence-monitoring-strategy-on-first-message":"On first message","presence-monitoring-strategy-on-each-message":"On each message","presence-monitoring-strategy-on-first-message-hint":"Reports presence status 'Inside' or 'Outside' on the first message after the configured minimal duration has passed since previous presence status 'Entered' or 'Left' update.","presence-monitoring-strategy-on-each-message-hint":"Reports presence status 'Inside' or 'Outside' on each message after presence status 'Entered' or 'Left' update.","fetch-credentials-to":"Fetch credentials to","add-originator-attributes-to":"Add originator attributes to","originator-attributes":"Originator attributes","fetch-latest-telemetry-with-timestamp":"Fetch latest telemetry with timestamp","fetch-latest-telemetry-with-timestamp-tooltip":'If selected, latest telemetry values will be added to the outbound metadata with timestamp, e.g: "{{latestTsKeyName}}": "{"ts":1574329385897, "value":42}"',"tell-failure":"Tell failure if any of the attributes are missing","tell-failure-tooltip":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"created-time":"Created time","chip-help":"Press 'Enter' to complete {{inputName}} input. \nPress 'Backspace' to delete {{inputName}}. \nMultiple values supported.",detail:"detail","field-name":"field name","device-profile":"device profile","entity-type":"entity type","message-type":"message type","timeseries-key":"time series key",type:"Type","first-name":"First name","last-name":"Last name",label:"Label","originator-fields-mapping":"Originator fields mapping","add-mapped-originator-fields-to":"Add mapped originator fields to",fields:"Fields","skip-empty-fields":"Skip empty fields","skip-empty-fields-tooltip":"Fields with empty values will not be added to the output message/output metadata.","fetch-interval":"Fetch interval","fetch-strategy":"Fetch strategy","fetch-timeseries-from-to":"Fetch time series from {{startInterval}} {{startIntervalTimeUnit}} ago to {{endInterval}} {{endIntervalTimeUnit}} ago.","fetch-timeseries-from-to-invalid":'Fetch time series invalid ("Interval start" should be less than "Interval end").',"use-metadata-dynamic-interval-tooltip":"If selected, the rule node will use dynamic interval start and end based on the message and metadata patterns.","all-mode-hint":'If selected fetch mode "All" rule node will retrieve telemetry from the fetch interval with configurable query parameters.',"first-mode-hint":'If selected fetch mode "First" rule node will retrieve the closest telemetry to the fetch interval\'s start.',"last-mode-hint":'If selected fetch mode "Last" rule node will retrieve the closest telemetry to the fetch interval\'s end.',ascending:"Ascending",descending:"Descending",min:"Min",max:"Max",average:"Average",sum:"Sum",count:"Count",none:"None","last-level-relation-tooltip":"If selected, the rule node will search related entities only on the level set in the max relation level.","last-level-device-relation-tooltip":"If selected, the rule node will search related devices only on the level set in the max relation level.","data-to-fetch":"Data to fetch","mapping-of-customers":"Mapping of customer's","map-fields-required":"All mapping fields are required.",attributes:"Attributes","related-device-attributes":"Related device attributes","add-selected-attributes-to":"Add selected attributes to","device-profiles":"Device profiles","mapping-of-tenant":"Mapping of tenant's","add-attribute-key":"Add attribute key","message-template":"Message template","message-template-required":"Message template is required","use-system-slack-settings":"Use system slack settings","slack-api-token":"Slack API token","slack-api-token-required":"Slack API token is required","keys-mapping":"keys mapping","add-key":"Add key",recipients:"Recipients","message-subject-and-content":"Message subject and content","template-rules-hint":"Both input fields support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the message metadata.","originator-customer-desc":"Use customer of incoming message originator as new originator.","originator-tenant-desc":"Use current tenant as new originator.","originator-related-entity-desc":"Use related entity as new originator. Lookup based on configured relation type and direction.","originator-alarm-originator-desc":"Use alarm originator as new originator. Only if incoming message originator is alarm entity.","originator-entity-by-name-pattern-desc":"Use entity fetched from DB as new originator. Lookup based on entity type and specified name pattern.","email-from-template-hint":"Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","recipients-block-main-hint":"Comma-separated address list. All input fields support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","forward-msg-default-rule-chain":"Forward message to the originator's default rule chain","forward-msg-default-rule-chain-tooltip":"If enabled, message will be forwarded to the originator's default rule chain, or rule chain from configuration, if originator has no default rule chain defined in the entity profile.","exclude-zero-deltas":"Exclude zero deltas from outbound message","exclude-zero-deltas-hint":'If enabled, the "{{outputValueKey}}" output key will be added to the outbound message if its value is not zero.',"exclude-zero-deltas-time-difference-hint":'If enabled, the "{{outputValueKey}}" and "{{periodValueKey}}" output keys will be added to the outbound message only if the "{{outputValueKey}}" value is not zero.',"search-direction-from":"From originator to target entity","search-direction-to":"From target entity to originator","del-relation-direction-from":"From originator","del-relation-direction-to":"To originator","target-entity":"Target entity","function-configuration":"Function configuration","function-name":"Function name","function-name-required":"Function name is required.",qualifier:"Qualifier","qualifier-hint":'If the qualifier is not specified, the default qualifier "$LATEST" will be used.',"aws-credentials":"AWS Credentials","connection-timeout":"Connection timeout","connection-timeout-required":"Connection timeout is required.","connection-timeout-min":"Min connection timeout is 0.","connection-timeout-hint":"The amount of time to wait in seconds when initially establishing a connection before giving up and timing out. A value of 0 means infinity, and is not recommended.","request-timeout":"Request timeout","request-timeout-required":"Request timeout is required","request-timeout-min":"Min request timeout is 0","request-timeout-hint":"The amount of time to wait in seconds for the request to complete before giving up and timing out. A value of 0 means infinity, and is not recommended.","tell-failure-aws-lambda":"Tell Failure if AWS Lambda function execution raises exception","tell-failure-aws-lambda-hint":"Rule node forces failure of message processing if AWS Lambda function execution raises exception."},"key-val":{key:"Key",value:"Value","see-examples":"See examples.","remove-entry":"Remove entry","remove-mapping-entry":"Remove mapping entry","add-mapping-entry":"Add mapping","add-entry":"Add entry","copy-key-values-from":"Copy key-values from","delete-key-values":"Delete key-values","delete-key-values-from":"Delete key-values from","at-least-one-key-error":"At least one key should be selected.","unique-key-value-pair-error":"'{{keyText}}' must be different from the '{{valText}}'!"},"mail-body-type":{"plain-text":"Plain text",html:"HTML",dynamic:"Dynamic","use-body-type-template":"Use body type template","plain-text-description":"Simple, unformatted text with no special styling or formating.","html-text-description":"Allows you to use HTML tags for formatting, links and images in your mai body.","dynamic-text-description":"Allows to use Plain Text or HTML body type dynamically based on templatization feature.","after-template-evaluation-hint":"After template evaluation value should be true for HTML, and false for Plain text."}}},!0)}(e),Wp(Yp,"tb-rule-node-core-config-css")}static{this.ɵfac=function(e){return new(e||Xp)(t.ɵɵinject(K.TranslateService))}}static{this.ɵmod=t.ɵɵdefineNgModule({type:Xp})}static{this.ɵinj=t.ɵɵdefineInjector({imports:[$,S,Ci,np,bo,hs,Hp,Jp,oe]})}}e("RuleNodeCoreConfigModule",Xp),("undefined"==typeof ngJitMode||ngJitMode)&&t.ɵɵsetNgModuleScope(Xp,{declarations:[oe],imports:[$,S],exports:[Ci,np,bo,hs,Hp,Jp,oe]})}}}));//# sourceMappingURL=rulenode-core-config.js.map diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java index 360fbce5b0..63432afc37 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java @@ -533,7 +533,7 @@ public class TbMathNodeTest { verify(ctx, timeout(TIMEOUT)).tellSuccess(msgCaptor.capture()); verify(telemetryService, times(1)).saveTimeseries(assertArg(request -> { assertThat(request.getEntries()).size().isOne(); - assertThat(request.isSaveLatest()).isTrue(); + assertThat(request.getStrategy()).isEqualTo(TimeseriesSaveRequest.Strategy.SAVE_ALL); })); TbMsg resultMsg = msgCaptor.getValue(); @@ -569,7 +569,7 @@ public class TbMathNodeTest { verify(ctx, timeout(TIMEOUT)).tellSuccess(msgCaptor.capture()); verify(telemetryService, times(1)).saveTimeseries(assertArg(request -> { assertThat(request.getEntries()).size().isOne(); - assertThat(request.isSaveLatest()).isTrue(); + assertThat(request.getStrategy()).isEqualTo(TimeseriesSaveRequest.Strategy.SAVE_ALL); })); TbMsg resultMsg = msgCaptor.getValue(); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNodeTest.java index cd7de994b5..3bcfe9f387 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNodeTest.java @@ -103,7 +103,7 @@ public class TbRabbitMqNodeTest { assertThat(config.getExchangeNamePattern()).isEqualTo(""); assertThat(config.getRoutingKeyPattern()).isEqualTo(""); assertThat(config.getMessageProperties()).isNull(); - assertThat(config.getHost()).isEqualTo(ConnectionFactory.DEFAULT_HOST); + assertThat(config.getHost()).isNull(); assertThat(config.getPort()).isEqualTo(ConnectionFactory.DEFAULT_AMQP_PORT); assertThat(config.getVirtualHost()).isEqualTo(ConnectionFactory.DEFAULT_VHOST); assertThat(config.getUsername()).isEqualTo(ConnectionFactory.DEFAULT_USER); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNodeTest.java index 2cba4b8fb3..892e217414 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNodeTest.java @@ -25,20 +25,23 @@ import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.MethodSource; import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import org.mockito.ThrowingConsumer; import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.AbstractRuleNodeUpgradeTest; import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TimeseriesSaveRequest; +import org.thingsboard.rule.engine.telemetry.strategy.ProcessingStrategy; import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; +import org.thingsboard.server.common.data.kv.DoubleDataEntry; import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.msg.TbMsgType; @@ -46,6 +49,8 @@ import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileCon import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.service.ConstraintValidator; import java.util.ArrayList; import java.util.List; @@ -55,24 +60,30 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.assertArg; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) -public class TbMsgTimeseriesNodeTest { +public class TbMsgTimeseriesNodeTest extends AbstractRuleNodeUpgradeTest { private final TenantId TENANT_ID = TenantId.fromUUID(UUID.fromString("c8f34868-603a-4433-876a-7d356e5cf377")); private final DeviceId DEVICE_ID = new DeviceId(UUID.fromString("e5095e9a-04f4-44c9-b443-1cf1b97d3384")); - private final TenantProfileId TENANT_PROFILE_ID = new TenantProfileId(UUID.fromString("ab78dd78-83d0-43fa-869f-d42ec9ed1744")); + + private TenantProfile tenantProfile; private TbMsgTimeseriesNode node; private TbMsgTimeseriesNodeConfiguration config; - private long tenantProfileDefaultStorageTtl; @Mock private TbContext ctxMock; @@ -81,29 +92,68 @@ public class TbMsgTimeseriesNodeTest { @BeforeEach public void setUp() throws TbNodeException { - node = new TbMsgTimeseriesNode(); + tenantProfile = new TenantProfile(new TenantProfileId(UUID.fromString("ab78dd78-83d0-43fa-869f-d42ec9ed1744"))); + var tenantProfileConfiguration = new DefaultTenantProfileConfiguration(); + tenantProfileConfiguration.setDefaultStorageTtlDays(5); + var tenantProfileData = new TenantProfileData(); + tenantProfileData.setConfiguration(tenantProfileConfiguration); + tenantProfile.setProfileData(tenantProfileData); + lenient().when(ctxMock.getTenantProfile()).thenReturn(tenantProfile); + + lenient().when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + lenient().when(ctxMock.getTelemetryService()).thenReturn(telemetryServiceMock); + + node = spy(new TbMsgTimeseriesNode()); config = new TbMsgTimeseriesNodeConfiguration().defaultConfiguration(); } @Test public void verifyDefaultConfig() { assertThat(config.getDefaultTTL()).isEqualTo(0L); - assertThat(config.isSkipLatestPersistence()).isFalse(); + assertThat(config.getProcessingSettings()).isInstanceOf(TbMsgTimeseriesNodeConfiguration.ProcessingSettings.OnEveryMessage.class); assertThat(config.isUseServerTs()).isFalse(); } + @Test + public void whenInit_thenShouldAddTenantProfileListener() throws Exception { + // GIVEN-WHEN + node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + // THEN + then(ctxMock).should().addTenantProfileListener(any()); + } + + @Test + public void givenProcessingSettingsAreNull_whenValidatingConstraints_thenThrowsException() { + // GIVEN + config.setProcessingSettings(null); + + // WHEN-THEN + assertThatThrownBy(() -> ConstraintValidator.validateFields(config)) + .isInstanceOf(DataValidationException.class) + .hasMessage("Validation error: processingSettings must not be null"); + } + @ParameterizedTest @EnumSource(TbMsgType.class) public void givenMsgTypeAndEmptyMsgData_whenOnMsg_thenVerifyFailureMsg(TbMsgType msgType) throws TbNodeException { - init(); + // GIVEN + node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + TbMsg msg = TbMsg.newMsg() .type(msgType) .originator(DEVICE_ID) .copyMetaData(TbMsgMetaData.EMPTY) .data(TbMsg.EMPTY_JSON_ARRAY) .build(); + + // WHEN node.onMsg(ctxMock, msg); + // THEN + then(ctxMock).should().addTenantProfileListener(any()); + then(ctxMock).should().getTenantProfile(); + ArgumentCaptor throwableCaptor = ArgumentCaptor.forClass(Throwable.class); verify(ctxMock).tellFailure(eq(msg), throwableCaptor.capture()); @@ -117,9 +167,11 @@ public class TbMsgTimeseriesNodeTest { } @Test - public void givenTtlFromConfigIsZeroAndUseServiceTsIsTrue_whenOnMsg_thenSaveTimeseriesUsingTenantProfileDefaultTtl() throws TbNodeException { + public void givenTtlFromConfigIsZeroAndUseServerTsIsTrue_whenOnMsg_thenSaveTimeseriesUsingTenantProfileDefaultTtl() throws TbNodeException { + // GIVEN config.setUseServerTs(true); - init(); + + node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); String data = """ { @@ -134,24 +186,29 @@ public class TbMsgTimeseriesNodeTest { .data(data) .build(); - when(ctxMock.getTelemetryService()).thenReturn(telemetryServiceMock); - when(ctxMock.getTenantId()).thenReturn(TENANT_ID); doAnswer(invocation -> { TimeseriesSaveRequest request = invocation.getArgument(0); request.getCallback().onSuccess(null); return null; }).when(telemetryServiceMock).saveTimeseries(any(TimeseriesSaveRequest.class)); + // WHEN node.onMsg(ctxMock, msg); + // THEN + then(ctxMock).should().getTenantId(); + then(ctxMock).should().getTelemetryService(); + then(ctxMock).should().addTenantProfileListener(any()); + then(ctxMock).should().getTenantProfile(); + List expectedList = getTsKvEntriesListWithTs(data, System.currentTimeMillis()); verify(telemetryServiceMock).saveTimeseries(assertArg(request -> { assertThat(request.getTenantId()).isEqualTo(TENANT_ID); assertThat(request.getCustomerId()).isNull(); assertThat(request.getEntityId()).isEqualTo(DEVICE_ID); assertThat(request.getEntries()).usingRecursiveFieldByFieldElementComparatorIgnoringFields("ts").containsExactlyElementsOf(expectedList); - assertThat(request.getTtl()).isEqualTo(tenantProfileDefaultStorageTtl); - assertThat(request.isSaveLatest()).isTrue(); + assertThat(request.getTtl()).isEqualTo(extractTtlAsSeconds(tenantProfile)); + assertThat(request.getStrategy()).isEqualTo(TimeseriesSaveRequest.Strategy.SAVE_ALL); assertThat(request.getCallback()).isInstanceOf(TelemetryNodeCallback.class); })); verify(ctxMock).tellSuccess(msg); @@ -159,11 +216,17 @@ public class TbMsgTimeseriesNodeTest { } @Test - public void givenSkipLatestPersistenceIsTrueAndTtlFromConfig_whenOnMsg_thenSaveTimeseriesUsingTtlFromConfig() throws TbNodeException { - long ttlFromConfig = 5L; - config.setDefaultTTL(ttlFromConfig); - config.setSkipLatestPersistence(true); - init(); + public void givenSkipLatestProcessingSettingsAndTtlFromConfig_whenOnMsg_thenSaveTimeseriesUsingTtlFromConfig() throws TbNodeException { + // GIVEN + config.setDefaultTTL(10L); + + var timeseriesStrategy = ProcessingStrategy.onEveryMessage(); + var latestStrategy = ProcessingStrategy.skip(); + var webSockets = ProcessingStrategy.onEveryMessage(); + var processingSettings = new TbMsgTimeseriesNodeConfiguration.ProcessingSettings.Advanced(timeseriesStrategy, latestStrategy, webSockets); + config.setProcessingSettings(processingSettings); + + node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); String data = """ { @@ -180,24 +243,29 @@ public class TbMsgTimeseriesNodeTest { .data(data) .build(); - when(ctxMock.getTelemetryService()).thenReturn(telemetryServiceMock); - when(ctxMock.getTenantId()).thenReturn(TENANT_ID); doAnswer(invocation -> { TimeseriesSaveRequest request = invocation.getArgument(0); request.getCallback().onSuccess(null); return null; }).when(telemetryServiceMock).saveTimeseries(any(TimeseriesSaveRequest.class)); + // WHEN node.onMsg(ctxMock, msg); + // THEN + then(ctxMock).should().getTenantId(); + then(ctxMock).should().getTelemetryService(); + then(ctxMock).should().addTenantProfileListener(any()); + then(ctxMock).should().getTenantProfile(); + List expectedList = getTsKvEntriesListWithTs(data, ts); verify(telemetryServiceMock).saveTimeseries(assertArg(request -> { assertThat(request.getTenantId()).isEqualTo(TENANT_ID); assertThat(request.getCustomerId()).isNull(); assertThat(request.getEntityId()).isEqualTo(DEVICE_ID); assertThat(request.getEntries()).containsExactlyElementsOf(expectedList); - assertThat(request.getTtl()).isEqualTo(ttlFromConfig); - assertThat(request.isSaveLatest()).isFalse(); + assertThat(request.getTtl()).isEqualTo(config.getDefaultTTL()); + assertThat(request.getStrategy()).isEqualTo(new TimeseriesSaveRequest.Strategy(true, false, true)); assertThat(request.getCallback()).isInstanceOf(TelemetryNodeCallback.class); })); verify(ctxMock).tellSuccess(msg); @@ -207,11 +275,10 @@ public class TbMsgTimeseriesNodeTest { @ParameterizedTest @MethodSource public void givenTtlFromConfigAndTtlFromMd_whenOnMsg_thenVerifyTtl(String ttlFromMd, long ttlFromConfig, long expectedTtl) throws TbNodeException { + // GIVEN config.setDefaultTTL(ttlFromConfig); - init(); - when(ctxMock.getTelemetryService()).thenReturn(telemetryServiceMock); - when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); String data = """ { @@ -227,14 +294,17 @@ public class TbMsgTimeseriesNodeTest { .copyMetaData(metadata) .data(data) .build(); + + // WHEN node.onMsg(ctxMock, msg); + // THEN verify(telemetryServiceMock).saveTimeseries(assertArg(request -> { assertThat(request.getTenantId()).isEqualTo(TENANT_ID); assertThat(request.getCustomerId()).isNull(); assertThat(request.getEntityId()).isEqualTo(DEVICE_ID); assertThat(request.getTtl()).isEqualTo(expectedTtl); - assertThat(request.isSaveLatest()).isTrue(); + assertThat(request.getStrategy()).isEqualTo(TimeseriesSaveRequest.Strategy.SAVE_ALL); assertThat(request.getCallback()).isInstanceOf(TelemetryNodeCallback.class); })); } @@ -251,26 +321,6 @@ public class TbMsgTimeseriesNodeTest { ); } - private void init() throws TbNodeException { - var configuration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); - var tenantProfile = getTenantProfile(); - when(ctxMock.getTenantProfile()).thenReturn(tenantProfile); - tenantProfile.getProfileConfiguration().ifPresent(profileConfiguration -> - tenantProfileDefaultStorageTtl = TimeUnit.DAYS.toSeconds(profileConfiguration.getDefaultStorageTtlDays())); - node.init(ctxMock, configuration); - verify(ctxMock).addTenantProfileListener(any()); - } - - private TenantProfile getTenantProfile() { - var tenantProfile = new TenantProfile(TENANT_PROFILE_ID); - var tenantProfileData = new TenantProfileData(); - var tenantProfileConfiguration = new DefaultTenantProfileConfiguration(); - tenantProfileConfiguration.setDefaultStorageTtlDays(5); - tenantProfileData.setConfiguration(tenantProfileConfiguration); - tenantProfile.setProfileData(tenantProfileData); - return tenantProfile; - } - private static List getTsKvEntriesListWithTs(String data, long ts) { Map> tsKvMap = JsonConverter.convertToTelemetry(JsonParser.parseString(data), ts); List expectedList = new ArrayList<>(); @@ -282,4 +332,309 @@ public class TbMsgTimeseriesNodeTest { return expectedList; } + @Test + public void givenOnEveryMessageProcessingSettingsAndSameMessageTwoTimes_whenOnMsg_thenPersistSameMessageTwoTimes() throws TbNodeException { + // GIVEN + config.setProcessingSettings(new TbMsgTimeseriesNodeConfiguration.ProcessingSettings.OnEveryMessage()); + + node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + var msg = TbMsg.newMsg() + .type(TbMsgType.POST_TELEMETRY_REQUEST) + .originator(DEVICE_ID) + .data(JacksonUtil.newObjectNode().put("temperature", 22.3).toString()) + .metaData(new TbMsgMetaData(Map.of("ts", "123"))) + .build(); + + // WHEN-THEN + var expectedSaveRequest = TimeseriesSaveRequest.builder() + .tenantId(TENANT_ID) + .customerId(msg.getCustomerId()) + .entityId(msg.getOriginator()) + .entry(new BasicTsKvEntry(123L, new DoubleDataEntry("temperature", 22.3))) + .ttl(extractTtlAsSeconds(tenantProfile)) + .strategy(TimeseriesSaveRequest.Strategy.SAVE_ALL) + .build(); + + node.onMsg(ctxMock, msg); + then(telemetryServiceMock).should(times(1)).saveTimeseries(assertArg( + actualSaveRequest -> assertThat(actualSaveRequest).usingRecursiveComparison().ignoringFields("callback").isEqualTo(expectedSaveRequest) + )); + + node.onMsg(ctxMock, msg); + then(telemetryServiceMock).should(times(2)).saveTimeseries(assertArg( + actualSaveRequest -> assertThat(actualSaveRequest).usingRecursiveComparison().ignoringFields("callback").isEqualTo(expectedSaveRequest) + )); + } + + @Test + public void givenDeduplicateProcessingSettingsAndSameMessageTwoTimes_whenOnMsg_thenPersistThisMessageOnlyFirstTime() throws TbNodeException { + // GIVEN + config.setProcessingSettings(new TbMsgTimeseriesNodeConfiguration.ProcessingSettings.Deduplicate(10)); + + node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + var msg = TbMsg.newMsg() + .type(TbMsgType.POST_TELEMETRY_REQUEST) + .originator(DEVICE_ID) + .data(JacksonUtil.newObjectNode().put("temperature", 22.3).toString()) + .metaData(new TbMsgMetaData(Map.of("ts", "123"))) + .build(); + + // WHEN-THEN + var expectedSaveRequest = TimeseriesSaveRequest.builder() + .tenantId(TENANT_ID) + .customerId(msg.getCustomerId()) + .entityId(msg.getOriginator()) + .entry(new BasicTsKvEntry(123L, new DoubleDataEntry("temperature", 22.3))) + .ttl(extractTtlAsSeconds(tenantProfile)) + .strategy(TimeseriesSaveRequest.Strategy.SAVE_ALL) + .build(); + + node.onMsg(ctxMock, msg); + then(telemetryServiceMock).should().saveTimeseries(assertArg( + actualSaveRequest -> assertThat(actualSaveRequest).usingRecursiveComparison().ignoringFields("callback").isEqualTo(expectedSaveRequest) + )); + + clearInvocations(telemetryServiceMock, ctxMock); + + node.onMsg(ctxMock, msg); + then(telemetryServiceMock).should(never()).saveTimeseries(any()); + } + + @Test + public void givenWebSocketsOnlyProcessingSettingsAndSameMessageTwoTimes_whenOnMsg_thenSendsOnlyWsUpdateTwoTimes() throws TbNodeException { + // GIVEN + config.setProcessingSettings(new TbMsgTimeseriesNodeConfiguration.ProcessingSettings.WebSocketsOnly()); + + node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + var msg = TbMsg.newMsg() + .type(TbMsgType.POST_TELEMETRY_REQUEST) + .originator(DEVICE_ID) + .data(JacksonUtil.newObjectNode().put("temperature", 22.3).toString()) + .metaData(new TbMsgMetaData(Map.of("ts", "123"))) + .build(); + + // WHEN-THEN + var expectedSaveRequest = TimeseriesSaveRequest.builder() + .tenantId(TENANT_ID) + .customerId(msg.getCustomerId()) + .entityId(msg.getOriginator()) + .entry(new BasicTsKvEntry(123L, new DoubleDataEntry("temperature", 22.3))) + .ttl(extractTtlAsSeconds(tenantProfile)) + .strategy(TimeseriesSaveRequest.Strategy.WS_ONLY) + .build(); + + node.onMsg(ctxMock, msg); + then(telemetryServiceMock).should(times(1)).saveTimeseries(assertArg( + actualSaveRequest -> assertThat(actualSaveRequest).usingRecursiveComparison().ignoringFields("callback").isEqualTo(expectedSaveRequest) + )); + + node.onMsg(ctxMock, msg); + then(telemetryServiceMock).should(times(2)).saveTimeseries(assertArg( + actualSaveRequest -> assertThat(actualSaveRequest).usingRecursiveComparison().ignoringFields("callback").isEqualTo(expectedSaveRequest) + )); + } + + @Test + public void givenAdvancedProcessingSettingsWithOnEveryMessageStrategiesForAllActionsAndSameMessageTwoTimes_whenOnMsg_thenPersistSameMessageTwoTimes() throws TbNodeException { + // GIVEN + config.setProcessingSettings(new TbMsgTimeseriesNodeConfiguration.ProcessingSettings.Advanced( + ProcessingStrategy.onEveryMessage(), + ProcessingStrategy.onEveryMessage(), + ProcessingStrategy.onEveryMessage() + )); + + node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + var msg = TbMsg.newMsg() + .type(TbMsgType.POST_TELEMETRY_REQUEST) + .originator(DEVICE_ID) + .data(JacksonUtil.newObjectNode().put("temperature", 22.3).toString()) + .metaData(new TbMsgMetaData(Map.of("ts", "123"))) + .build(); + + // WHEN-THEN + var expectedSaveRequest = TimeseriesSaveRequest.builder() + .tenantId(TENANT_ID) + .customerId(msg.getCustomerId()) + .entityId(msg.getOriginator()) + .entry(new BasicTsKvEntry(123L, new DoubleDataEntry("temperature", 22.3))) + .ttl(extractTtlAsSeconds(tenantProfile)) + .strategy(TimeseriesSaveRequest.Strategy.SAVE_ALL) + .build(); + + node.onMsg(ctxMock, msg); + then(telemetryServiceMock).should(times(1)).saveTimeseries(assertArg( + actualSaveRequest -> assertThat(actualSaveRequest).usingRecursiveComparison().ignoringFields("callback").isEqualTo(expectedSaveRequest) + )); + + node.onMsg(ctxMock, msg); + then(telemetryServiceMock).should(times(2)).saveTimeseries(assertArg( + actualSaveRequest -> assertThat(actualSaveRequest).usingRecursiveComparison().ignoringFields("callback").isEqualTo(expectedSaveRequest) + )); + } + + @Test + public void givenAdvancedProcessingSettingsWithDifferentDeduplicateStrategyForEachAction_whenOnMsg_thenEvaluatesStrategiesForEachActionsIndependently() throws TbNodeException { + // GIVEN + config.setProcessingSettings(new TbMsgTimeseriesNodeConfiguration.ProcessingSettings.Advanced( + ProcessingStrategy.deduplicate(1), + ProcessingStrategy.deduplicate(2), + ProcessingStrategy.deduplicate(3) + )); + + node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + long ts1 = 500L; + long ts2 = 1500L; + long ts3 = 2500L; + + // WHEN-THEN + node.onMsg(ctxMock, TbMsg.newMsg() + .type(TbMsgType.POST_TELEMETRY_REQUEST) + .originator(DEVICE_ID) + .data(JacksonUtil.newObjectNode().put("temperature", 22.3).toString()) + .metaData(new TbMsgMetaData(Map.of("ts", Long.toString(ts1)))) + .build()); + then(telemetryServiceMock).should().saveTimeseries(assertArg( + actualSaveRequest -> assertThat(actualSaveRequest.getStrategy()).isEqualTo(TimeseriesSaveRequest.Strategy.SAVE_ALL) + )); + + clearInvocations(telemetryServiceMock); + + node.onMsg(ctxMock, TbMsg.newMsg() + .type(TbMsgType.POST_TELEMETRY_REQUEST) + .originator(DEVICE_ID) + .data(JacksonUtil.newObjectNode().put("temperature", 22.3).toString()) + .metaData(new TbMsgMetaData(Map.of("ts", Long.toString(ts2)))) + .build()); + then(telemetryServiceMock).should().saveTimeseries(assertArg( + actualSaveRequest -> assertThat(actualSaveRequest.getStrategy()).isEqualTo(new TimeseriesSaveRequest.Strategy(true, false, false)) + )); + + clearInvocations(telemetryServiceMock); + + node.onMsg(ctxMock, TbMsg.newMsg() + .type(TbMsgType.POST_TELEMETRY_REQUEST) + .originator(DEVICE_ID) + .data(JacksonUtil.newObjectNode().put("temperature", 22.3).toString()) + .metaData(new TbMsgMetaData(Map.of("ts", Long.toString(ts3)))) + .build()); + then(telemetryServiceMock).should().saveTimeseries(assertArg( + actualSaveRequest -> assertThat(actualSaveRequest.getStrategy()).isEqualTo(new TimeseriesSaveRequest.Strategy(true, true, false)) + )); + } + + @Test + public void givenAdvancedProcessingSettingsWithSkipStrategiesForAllActionsAndSameMessageTwoTimes_whenOnMsg_thenSkipsSameMessageTwoTimes() throws TbNodeException { + // GIVEN + config.setProcessingSettings(new TbMsgTimeseriesNodeConfiguration.ProcessingSettings.Advanced( + ProcessingStrategy.skip(), + ProcessingStrategy.skip(), + ProcessingStrategy.skip() + )); + + node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + var msg = TbMsg.newMsg() + .type(TbMsgType.POST_TELEMETRY_REQUEST) + .originator(DEVICE_ID) + .data(JacksonUtil.newObjectNode().put("temperature", 22.3).toString()) + .metaData(new TbMsgMetaData(Map.of("ts", "123"))) + .build(); + + // WHEN-THEN + node.onMsg(ctxMock, msg); + then(telemetryServiceMock).should(never()).saveTimeseries(any()); + then(ctxMock).should(times(1)).tellSuccess(msg); + + node.onMsg(ctxMock, msg); + then(telemetryServiceMock).should(never()).saveTimeseries(any()); + then(ctxMock).should(times(2)).tellSuccess(msg); + } + + private static long extractTtlAsSeconds(TenantProfile tenantProfile) { + return TimeUnit.DAYS.toSeconds(tenantProfile.getDefaultProfileConfiguration().getDefaultStorageTtlDays()); + } + + @Override + protected TbNode getTestNode() { + return node; + } + + private static Stream givenFromVersionAndConfig_whenUpgrade_thenVerifyHasChangesAndConfig() { + return Stream.of( + Arguments.of(0, """ + { + "defaultTTL": 0, + "useServerTs": false, + "skipLatestPersistence": false + }""", + true, + """ + { + "defaultTTL": 0, + "useServerTs": false, + "processingSettings": { + "type": "ON_EVERY_MESSAGE" + } + }"""), + Arguments.of(0, """ + { + "defaultTTL": 0, + "useServerTs": false + }""", + true, + """ + { + "defaultTTL": 0, + "useServerTs": false, + "processingSettings": { + "type": "ON_EVERY_MESSAGE" + } + }"""), + Arguments.of(0, """ + { + "defaultTTL": 0, + "useServerTs": false, + "skipLatestPersistence": null + }""", + true, + """ + { + "defaultTTL": 0, + "useServerTs": false, + "processingSettings": { + "type": "ON_EVERY_MESSAGE" + } + }"""), + Arguments.of(0, """ + { + "defaultTTL": 0, + "useServerTs": false, + "skipLatestPersistence": true + }""", + true, + """ + { + "defaultTTL": 0, + "useServerTs": false, + "processingSettings": { + "type": "ADVANCED", + "timeseries": { + "type": "ON_EVERY_MESSAGE" + }, + "latest": { + "type": "SKIP" + }, + "webSockets": { + "type": "ON_EVERY_MESSAGE" + } + } + }""") + ); + } + } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/strategy/DeduplicateProcessingStrategyTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/strategy/DeduplicateProcessingStrategyTest.java new file mode 100644 index 0000000000..2c81ecd0a1 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/strategy/DeduplicateProcessingStrategyTest.java @@ -0,0 +1,260 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.telemetry.strategy; + +import com.github.benmanes.caffeine.cache.LoadingCache; +import com.github.benmanes.caffeine.cache.Policy; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import java.time.Duration; +import java.util.Set; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class DeduplicateProcessingStrategyTest { + + final int deduplicationIntervalSecs = 10; + + DeduplicateProcessingStrategy strategy; + + @BeforeEach + void setup() { + strategy = new DeduplicateProcessingStrategy(deduplicationIntervalSecs); + } + + @Test + void shouldThrowWhenDeduplicationIntervalIsLessThanOneSecond() { + assertThatThrownBy(() -> new DeduplicateProcessingStrategy(0)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Deduplication interval must be at least 1 second(s) and at most 86400 second(s), was 0 second(s)"); + } + + @Test + void shouldThrowWhenDeduplicationIntervalIsMoreThan24Hours() { + assertThatThrownBy(() -> new DeduplicateProcessingStrategy(86401)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Deduplication interval must be at least 1 second(s) and at most 86400 second(s), was 86401 second(s)"); + } + + @Test + void shouldUseAtLeastTenMinutesForExpireAfterAccess() { + // GIVEN + int deduplicationIntervalSecs = 1; // min deduplication interval duration + + // WHEN + strategy = new DeduplicateProcessingStrategy(deduplicationIntervalSecs); + + // THEN + var deduplicationCache = (LoadingCache>) ReflectionTestUtils.getField(strategy, "deduplicationCache"); + + assertThat(deduplicationCache.policy().expireAfterAccess()) + .isPresent() + .map(Policy.FixedExpiration::getExpiresAfter) + .hasValue(Duration.ofMinutes(10L)); + } + + @Test + void shouldCalculateExpireAfterAccessAsIntervalDurationMultipliedByTen() { + // GIVEN + int deduplicationIntervalSecs = (int) Duration.ofHours(1L).toSeconds(); // max deduplication interval duration + + // WHEN + strategy = new DeduplicateProcessingStrategy(deduplicationIntervalSecs); + + // THEN + var deduplicationCache = (LoadingCache>) ReflectionTestUtils.getField(strategy, "deduplicationCache"); + + assertThat(deduplicationCache.policy().expireAfterAccess()) + .isPresent() + .map(Policy.FixedExpiration::getExpiresAfter) + .hasValue(Duration.ofHours(10L)); + } + + @Test + void shouldUseAtMostTwoDaysForExpireAfterAccess() { + // GIVEN + int deduplicationIntervalSecs = (int) Duration.ofDays(1L).toSeconds(); // max deduplication interval duration + + // WHEN + strategy = new DeduplicateProcessingStrategy(deduplicationIntervalSecs); + + // THEN + var deduplicationCache = (LoadingCache>) ReflectionTestUtils.getField(strategy, "deduplicationCache"); + + assertThat(deduplicationCache.policy().expireAfterAccess()) + .isPresent() + .map(Policy.FixedExpiration::getExpiresAfter) + .hasValue(Duration.ofDays(2L)); + } + + @Test + void shouldNotAllowMoreThan100DeduplicationIntervals() { + // GIVEN + int deduplicationIntervalSecs = 1; // min deduplication interval duration + + // WHEN + strategy = new DeduplicateProcessingStrategy(deduplicationIntervalSecs); + + // THEN + var deduplicationCache = (LoadingCache>) ReflectionTestUtils.getField(strategy, "deduplicationCache"); + + assertThat(deduplicationCache.policy().eviction()) + .isPresent() + .map(Policy.Eviction::getMaximum) + .hasValue(100L); + } + + @Test + void shouldCalculateMaxIntervalsAsTwoDaysDividedByIntervalDuration() { + // GIVEN + int deduplicationIntervalSecs = (int) Duration.ofHours(1L).toSeconds(); + + // WHEN + strategy = new DeduplicateProcessingStrategy(deduplicationIntervalSecs); + + // THEN + var deduplicationCache = (LoadingCache>) ReflectionTestUtils.getField(strategy, "deduplicationCache"); + + assertThat(deduplicationCache.policy().eviction()) + .isPresent() + .map(Policy.Eviction::getMaximum) + .hasValue(48L); + } + + @Test + void shouldKeepAtLeastTwoDeduplicationIntervals() { + // GIVEN + int deduplicationIntervalSecs = (int) Duration.ofDays(1L).toSeconds(); // max deduplication interval duration + + // WHEN + strategy = new DeduplicateProcessingStrategy(deduplicationIntervalSecs); + + // THEN + var deduplicationCache = (LoadingCache>) ReflectionTestUtils.getField(strategy, "deduplicationCache"); + + assertThat(deduplicationCache.policy().eviction()) + .isPresent() + .map(Policy.Eviction::getMaximum) + .hasValue(2L); + } + + @Test + void shouldReturnTrueForFirstCallInInterval() { + long ts = 1_000_000L; + UUID originator = UUID.randomUUID(); + + assertThat(strategy.shouldProcess(ts, originator)).isTrue(); + } + + @Test + void shouldReturnFalseForSubsequentCallsInInterval() { + long baseTs = 1_000_000L; + UUID originator = UUID.randomUUID(); + + // Initial call should return true + assertThat(strategy.shouldProcess(baseTs, originator)).isTrue(); + + // Subsequent call within the same interval should return false for the same originator + long withinSameIntervalTs = baseTs + 1000L; + assertThat(strategy.shouldProcess(withinSameIntervalTs, originator)).isFalse(); + } + + @Test + void shouldHandleMultipleOriginatorsIndependently() { + long baseTs = 1_000_000L; + UUID originator1 = UUID.randomUUID(); + UUID originator2 = UUID.randomUUID(); + + // First call for different originators in the same interval should return true independently + assertThat(strategy.shouldProcess(baseTs, originator1)).isTrue(); + assertThat(strategy.shouldProcess(baseTs, originator2)).isTrue(); + + // Subsequent calls for the same originators within the same interval should return false + assertThat(strategy.shouldProcess(baseTs + 500L, originator1)).isFalse(); + assertThat(strategy.shouldProcess(baseTs + 500L, originator2)).isFalse(); + } + + @Test + void shouldHandleEdgeCaseTimestamps() { + long minTs = Long.MIN_VALUE; + long maxTs = Long.MAX_VALUE; + UUID originator = UUID.randomUUID(); + + assertThat(strategy.shouldProcess(minTs, originator)).isTrue(); + assertThat(strategy.shouldProcess(minTs + 1L, originator)).isFalse(); + + assertThat(strategy.shouldProcess(maxTs, originator)).isTrue(); + assertThat(strategy.shouldProcess(maxTs - 1L, originator)).isFalse(); + } + + @Test + void shouldResetDeduplicationAtIntervalBoundaries() { + UUID originator = UUID.randomUUID(); + + // check 1st interval + long firstIntervalStart = 0L; + long firstIntervalEnd = firstIntervalStart + Duration.ofSeconds(deduplicationIntervalSecs).toMillis() - 1L; + long firstIntervalMiddle = calculateMiddle(firstIntervalStart, firstIntervalEnd); + + assertThat(strategy.shouldProcess(firstIntervalStart, originator)).isTrue(); + assertThat(strategy.shouldProcess(firstIntervalStart + 1, originator)).isFalse(); + assertThat(strategy.shouldProcess(firstIntervalMiddle, originator)).isFalse(); + assertThat(strategy.shouldProcess(firstIntervalEnd - 1, originator)).isFalse(); + assertThat(strategy.shouldProcess(firstIntervalEnd, originator)).isFalse(); + + // check 2nd interval + long secondIntervalStart = firstIntervalEnd + 1L; + long secondIntervalEnd = secondIntervalStart + Duration.ofSeconds(deduplicationIntervalSecs).toMillis() - 1L; + long secondIntervalMiddle = calculateMiddle(secondIntervalStart, secondIntervalEnd); + + assertThat(strategy.shouldProcess(secondIntervalStart, originator)).isTrue(); + assertThat(strategy.shouldProcess(secondIntervalStart + 1, originator)).isFalse(); + assertThat(strategy.shouldProcess(secondIntervalMiddle, originator)).isFalse(); + assertThat(strategy.shouldProcess(secondIntervalEnd - 1, originator)).isFalse(); + assertThat(strategy.shouldProcess(secondIntervalEnd, originator)).isFalse(); + } + + @Test + void shouldHandleMultipleOriginatorsOverMultipleIntervals() { + UUID originator1 = UUID.randomUUID(); + UUID originator2 = UUID.randomUUID(); + long baseTs = 0L; + + // First interval for both originators + assertThat(strategy.shouldProcess(baseTs, originator1)).isTrue(); + assertThat(strategy.shouldProcess(baseTs, originator2)).isTrue(); + + // Move to the next interval + long nextIntervalTs = baseTs + Duration.ofSeconds(10).toMillis(); + + // Each originator should be allowed again in the new interval + assertThat(strategy.shouldProcess(nextIntervalTs, originator1)).isTrue(); + assertThat(strategy.shouldProcess(nextIntervalTs, originator2)).isTrue(); + + // Subsequent calls in the same new interval should return false + assertThat(strategy.shouldProcess(nextIntervalTs + 500L, originator1)).isFalse(); + assertThat(strategy.shouldProcess(nextIntervalTs + 500L, originator2)).isFalse(); + } + + private static long calculateMiddle(long start, long end) { + return start + (end - start) / 2; + } + +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/strategy/OnEveryMessageProcessingStrategyTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/strategy/OnEveryMessageProcessingStrategyTest.java new file mode 100644 index 0000000000..36858418c3 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/strategy/OnEveryMessageProcessingStrategyTest.java @@ -0,0 +1,48 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.telemetry.strategy; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.UUID; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +class OnEveryMessageProcessingStrategyTest { + + @ParameterizedTest + @MethodSource("edgeCaseProvider") + void shouldAlwaysReturnTrueForAnyInput(long timestamp, UUID originator) { + var onEveryMessage = OnEveryMessageProcessingStrategy.getInstance(); + assertThat(onEveryMessage.shouldProcess(timestamp, originator)).isTrue(); + } + + private static Stream edgeCaseProvider() { + return Stream.of( + Arguments.of(Long.MIN_VALUE, new UUID(0L, 0L)), + Arguments.of(Long.MAX_VALUE, new UUID(Long.MAX_VALUE, Long.MAX_VALUE)), + Arguments.of(0L, new UUID(0L, 0L)), + Arguments.of(-1L, new UUID(-1L, -1L)), + Arguments.of(1L, new UUID(1L, 1L)), + Arguments.of(42L, UUID.randomUUID()), + Arguments.of(1000L, null) + ); + } + +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/strategy/ProcessingStrategyTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/strategy/ProcessingStrategyTest.java new file mode 100644 index 0000000000..45fed469d2 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/strategy/ProcessingStrategyTest.java @@ -0,0 +1,55 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.telemetry.strategy; + +import org.junit.jupiter.api.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import java.time.Duration; + +import static org.assertj.core.api.Assertions.assertThat; + +class ProcessingStrategyTest { + + @Test + void testOnEveryMessageReturnsCorrectInstance() { + ProcessingStrategy strategy = ProcessingStrategy.onEveryMessage(); + assertThat(strategy) + .isNotNull() + .isInstanceOf(OnEveryMessageProcessingStrategy.class); + } + + @Test + void testDeduplicateReturnsCorrectInstance() { + int validDeduplicationIntervalSecs = 5; + ProcessingStrategy strategy = ProcessingStrategy.deduplicate(validDeduplicationIntervalSecs); + assertThat(strategy) + .isNotNull() + .isInstanceOf(DeduplicateProcessingStrategy.class); + + long actualDeduplicationIntervalMillis = (long) ReflectionTestUtils.getField(strategy, "deduplicationIntervalMillis"); + assertThat(actualDeduplicationIntervalMillis).isEqualTo(Duration.ofSeconds(validDeduplicationIntervalSecs).toMillis()); + } + + @Test + void testSkipReturnsCorrectInstance() { + ProcessingStrategy strategy = ProcessingStrategy.skip(); + assertThat(strategy) + .isNotNull() + .isInstanceOf(SkipProcessingStrategy.class); + } + +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/strategy/SkipProcessingStrategyTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/strategy/SkipProcessingStrategyTest.java new file mode 100644 index 0000000000..ffdf2fc87e --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/strategy/SkipProcessingStrategyTest.java @@ -0,0 +1,48 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.telemetry.strategy; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.UUID; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +class SkipProcessingStrategyTest { + + @ParameterizedTest + @MethodSource("edgeCaseProvider") + void shouldAlwaysReturnFalseForAnyInput(long timestamp, UUID originator) { + var skipStrategy = SkipProcessingStrategy.getInstance(); + assertThat(skipStrategy.shouldProcess(timestamp, originator)).isFalse(); + } + + private static Stream edgeCaseProvider() { + return Stream.of( + Arguments.of(Long.MIN_VALUE, new UUID(0L, 0L)), + Arguments.of(Long.MAX_VALUE, new UUID(Long.MAX_VALUE, Long.MAX_VALUE)), + Arguments.of(0L, new UUID(0L, 0L)), + Arguments.of(-1L, new UUID(-1L, -1L)), + Arguments.of(1L, new UUID(1L, 1L)), + Arguments.of(42L, UUID.randomUUID()), + Arguments.of(1000L, null) + ); + } + +} diff --git a/ui-ngx/src/app/core/api/entity-data-subscription.ts b/ui-ngx/src/app/core/api/entity-data-subscription.ts index 46cd2af7ab..daeb6b8faf 100644 --- a/ui-ngx/src/app/core/api/entity-data-subscription.ts +++ b/ui-ngx/src/app/core/api/entity-data-subscription.ts @@ -685,7 +685,7 @@ export class EntityDataSubscription { if (this.tsFields.length > 0) { if (this.history) { cmd.historyCmd = { - keys: this.tsFields.map(key => key.key), + keys: [... new Set(this.tsFields.map(key => key.key))], startTs: this.subsTw.fixedWindow.startTimeMs, endTs: this.subsTw.fixedWindow.endTimeMs, interval: 0, @@ -702,7 +702,7 @@ export class EntityDataSubscription { } } else { cmd.tsCmd = { - keys: this.tsFields.map(key => key.key), + keys: [... new Set(this.tsFields.map(key => key.key))], startTs: this.subsTw.startTs, timeWindow: this.subsTw.aggregation.timeWindow, interval: 0, diff --git a/ui-ngx/src/app/core/http/domain.service.ts b/ui-ngx/src/app/core/http/domain.service.ts index bb6cd4a16b..fe05305c6f 100644 --- a/ui-ngx/src/app/core/http/domain.service.ts +++ b/ui-ngx/src/app/core/http/domain.service.ts @@ -32,9 +32,12 @@ export class DomainService { ) { } - public saveDomain(domain: Domain, oauth2ClientIds: Array, config?: RequestConfig): Observable { - return this.http.post(`/api/domain?oauth2ClientIds=${oauth2ClientIds.join(',')}`, - domain, defaultHttpOptionsFromConfig(config)); + public saveDomain(domain: Domain, oauth2ClientIds?: Array, config?: RequestConfig): Observable { + let url = '/api/domain'; + if (oauth2ClientIds?.length) { + url += `?oauth2ClientIds=${oauth2ClientIds.join(',')}`; + } + return this.http.post(url, domain, defaultHttpOptionsFromConfig(config)); } public updateOauth2Clients(id: string, oauth2ClientIds: Array, config?: RequestConfig): Observable { diff --git a/ui-ngx/src/app/core/http/rule-chain.service.ts b/ui-ngx/src/app/core/http/rule-chain.service.ts index e3353989cc..0e857c4570 100644 --- a/ui-ngx/src/app/core/http/rule-chain.service.ts +++ b/ui-ngx/src/app/core/http/rule-chain.service.ts @@ -33,6 +33,7 @@ import { LinkLabel, RuleNodeComponentDescriptor, RuleNodeConfiguration, + RuleNodeConfigurationComponent, ScriptLanguage, TestScriptInputParams, TestScriptResult @@ -180,6 +181,10 @@ export class RuleChainService { return this.http.post(url, inputParams, defaultHttpOptionsFromConfig(config)); } + public registerSystemRuleNodeConfigModule(module: any) { + Object.assign(this.ruleNodeConfigComponents, this.resourcesService.extractComponentsFromModule(module, RuleNodeConfigurationComponent, true)); + } + private loadRuleNodeComponents(ruleChainType: RuleChainType, config?: RequestConfig): Observable> { return this.componentDescriptorService.getComponentDescriptorsByTypes(ruleNodeTypeComponentTypes, ruleChainType, config).pipe( map((components) => { @@ -211,7 +216,7 @@ export class RuleChainService { Observable { const nodeDefinition = component.configurationDescriptor.nodeDefinition; const uiResources = nodeDefinition.uiResources; - if (uiResources && uiResources.length) { + if (!this.ruleNodeConfigComponents[nodeDefinition.configDirective] && uiResources && uiResources.length) { const commonResources = uiResources.filter((resource) => !resource.endsWith('.js')); const moduleResource = uiResources.find((resource) => resource.endsWith('.js')); const tasks: Observable[] = []; diff --git a/ui-ngx/src/app/core/http/widget.service.ts b/ui-ngx/src/app/core/http/widget.service.ts index ebf390866c..030d53de6f 100644 --- a/ui-ngx/src/app/core/http/widget.service.ts +++ b/ui-ngx/src/app/core/http/widget.service.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Injectable } from '@angular/core'; +import { Injectable, Type } from '@angular/core'; import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; import { Observable, of, ReplaySubject } from 'rxjs'; import { HttpClient } from '@angular/common/http'; @@ -24,7 +24,10 @@ import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; import { BaseWidgetType, DeprecatedFilter, - fullWidgetTypeFqn, migrateWidgetTypeToDynamicForms, + fullWidgetTypeFqn, + IWidgetSettingsComponent, + migrateWidgetTypeToDynamicForms, + WidgetSettingsComponent, WidgetType, widgetType, WidgetTypeDetails, @@ -36,6 +39,11 @@ import { filter, map, mergeMap, tap } from 'rxjs/operators'; import { WidgetTypeId } from '@shared/models/id/widget-type-id'; import { NULL_UUID } from '@shared/models/id/has-uuid'; import { ActivationEnd, Router } from '@angular/router'; +import { + BasicWidgetConfigComponent, + IBasicWidgetConfigComponent +} from '@home/components/widget/config/widget-config.component.models'; +import { ResourcesService } from '@core/services/resources.service'; @Injectable({ providedIn: 'root' @@ -50,9 +58,13 @@ export class WidgetService { private loadWidgetsBundleCacheSubject: ReplaySubject; + private basicWidgetSettingsComponentsMap: { [key: string]: Type } = {}; + private widgetSettingsComponentsMap: { [key: string]: Type } = {}; + constructor( private http: HttpClient, - private router: Router + private router: Router, + private resourcesService: ResourcesService, ) { this.router.events.pipe(filter(event => event instanceof ActivationEnd)).subscribe( () => { @@ -287,6 +299,30 @@ export class WidgetService { this.widgetsInfoInMemoryCache.set(widgetInfo.fullFqn, widgetInfo); } + public registerBasicWidgetConfigComponents(module: any) { + Object.assign(this.basicWidgetSettingsComponentsMap, this.resourcesService.extractComponentsFromModule(module, BasicWidgetConfigComponent)); + } + + public getBasicWidgetSettingsComponentBySelector(selector: string): Type { + return this.basicWidgetSettingsComponentsMap[selector]; + } + + public putBasicWidgetSettingsComponentToMap(selector: string, compType: Type) { + this.basicWidgetSettingsComponentsMap[selector] = compType; + } + + public registerWidgetSettingsComponents(module: any) { + Object.assign(this.widgetSettingsComponentsMap, this.resourcesService.extractComponentsFromModule(module, WidgetSettingsComponent)); + } + + public getWidgetSettingsComponentTypeBySelector(selector: string): Type { + return this.widgetSettingsComponentsMap[selector]; + } + + public putWidgetSettingsComponentToMap(selector: string, compType: Type) { + this.widgetSettingsComponentsMap[selector] = compType; + } + private widgetTypeUpdated(updatedWidgetType: BaseWidgetType): void { this.deleteWidgetInfoFromCache(fullWidgetTypeFqn(updatedWidgetType)); } diff --git a/ui-ngx/src/app/core/services/resources.service.ts b/ui-ngx/src/app/core/services/resources.service.ts index f5cba07a47..e2ee21c738 100644 --- a/ui-ngx/src/app/core/services/resources.service.ts +++ b/ui-ngx/src/app/core/services/resources.service.ts @@ -31,7 +31,7 @@ import { forkJoin, from, Observable, ReplaySubject, throwError } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { IModulesMap } from '@modules/common/modules-map.models'; import { TbResourceId } from '@shared/models/id/tb-resource-id'; -import { isObject } from '@core/utils'; +import { camelCase, isObject } from '@core/utils'; import { AuthService } from '@core/auth/auth.service'; import { select, Store } from '@ngrx/store'; import { selectIsAuthenticated } from '@core/auth/auth.selectors'; @@ -51,6 +51,8 @@ export interface ModulesWithComponents { standaloneComponents: ɵComponentDef[]; } +export type ComponentsSelectorMap = Record>; + export const flatModulesWithComponents = (modulesWithComponentsList: ModulesWithComponents[]): ModulesWithComponents => { const modulesWithComponents: ModulesWithComponents = { modules: [], @@ -91,6 +93,17 @@ export const componentTypeBySelector = (modulesWithComponents: ModulesWithCompon const matchesSelector = (selectors: ɵCssSelectorList, selector: string) => selectors.some(s => s.some(s1 => typeof s1 === 'string' && s1 === selector)); +const extractSelectorFromComponent = (comp: ɵComponentDef): string => { + for (const selectors of comp.selectors) { + for (const selector of selectors) { + if (typeof selector === 'string') { + return selector; + } + } + } + return null; +} + @Injectable({ providedIn: 'root' }) @@ -252,6 +265,30 @@ export class ResourcesService { ); } + public extractComponentsFromModule(module: any, instanceFilter?: any, isCamelCaseSelector = false): ComponentsSelectorMap { + const modulesWithComponents = this.extractModulesWithComponents(module); + const componentMap: ComponentsSelectorMap = {}; + + const processComponents = (components: Array<ɵComponentDef>) => { + components.forEach(item => { + if (instanceFilter && !(item.type.prototype instanceof instanceFilter)) { + return; + } + let selector = extractSelectorFromComponent(item); + if (isCamelCaseSelector) { + selector = camelCase(selector); + } + componentMap[selector] = item.type; + }); + }; + + processComponents(modulesWithComponents.standaloneComponents); + + modulesWithComponents.modules.forEach(module => { + processComponents(module.components); + }) + return componentMap; + } private extractModulesWithComponents(module: any, modulesWithComponents: ModulesWithComponents = { @@ -284,7 +321,7 @@ export class ResourcesService { modulesWithComponents.standaloneComponents.push(component); } } else { - this.extractModulesWithComponents(module, modulesWithComponents, visitedModules); + this.extractModulesWithComponents(element, modulesWithComponents, visitedModules); } } } else if (ɵNG_COMP_DEF in module) { diff --git a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html index a6b204b279..c56aae3476 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html +++ b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html @@ -192,7 +192,8 @@ (click)="$event.stopPropagation();"> - + diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-chips.component.html b/ui-ngx/src/app/modules/home/components/entity/entity-chips.component.html index c81857689f..c162540b0c 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity-chips.component.html +++ b/ui-ngx/src/app/modules/home/components/entity/entity-chips.component.html @@ -18,5 +18,5 @@ - {{ subEntity.name }} + {{ subEntity.name | customTranslate }} diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-chips.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity-chips.component.ts index 6aad28c638..ebece2186b 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity-chips.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entity-chips.component.ts @@ -18,7 +18,7 @@ import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; import { BaseData } from '@shared/models/base-data'; import { EntityId } from '@shared/models/id/entity-id'; import { baseDetailsPageByEntityType, EntityType } from '@app/shared/public-api'; -import { isEqual, isObject } from '@core/utils'; +import { isEqual, isNotEmptyStr, isObject } from '@core/utils'; @Component({ selector: 'tb-entity-chips', @@ -33,6 +33,9 @@ export class EntityChipsComponent implements OnChanges { @Input() key: string; + @Input() + detailsPagePrefixUrl: string; + entityDetailsPrefixUrl: string; subEntities: Array> = []; @@ -52,7 +55,9 @@ export class EntityChipsComponent implements OnChanges { if (isObject(entitiesList) && !Array.isArray(entitiesList)) { entitiesList = [entitiesList]; } - if (Array.isArray(entitiesList)) { + if (isNotEmptyStr(this.detailsPagePrefixUrl)) { + this.entityDetailsPrefixUrl = this.detailsPagePrefixUrl; + } else if (Array.isArray(entitiesList)) { if (entitiesList.length) { this.entityDetailsPrefixUrl = baseDetailsPageByEntityType.get(entitiesList[0].id.entityType as EntityType); } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/device-profile-transport-configuration.component.ts index 47d782dbdb..a295ef76ab 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/device-profile-transport-configuration.component.ts @@ -103,7 +103,7 @@ export class DeviceProfileTransportConfigurationComponent implements ControlValu delete configuration.type; } setTimeout(() => { - this.deviceProfileTransportConfigurationFormGroup.patchValue({configuration}); + this.deviceProfileTransportConfigurationFormGroup.patchValue({configuration}, {emitEvent: false}); }, 0); } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts index 775a5c963e..dbcd38a46f 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts @@ -190,7 +190,7 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V private fetchListObjects = (searchText: string): Observable> => { this.searchText = searchText; const pageLink = new PageLink(PAGE_SIZE_LIMIT, 0, this.searchText, { - property: 'id', + property: 'resourceKey', direction: Direction.ASC }); return this.deviceProfileService.getLwm2mObjectsPage(pageLink); diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-table.component.html b/ui-ngx/src/app/modules/home/components/relation/relation-table.component.html index fb49f299e9..4b0d4307f7 100644 --- a/ui-ngx/src/app/modules/home/components/relation/relation-table.component.html +++ b/ui-ngx/src/app/modules/home/components/relation/relation-table.component.html @@ -118,7 +118,7 @@ {{ 'relation.to-entity-name' | translate }} - {{ relation.toName }} + {{ relation.toName | customTranslate }} @@ -132,7 +132,7 @@ {{ 'relation.from-entity-name' | translate }} - {{ relation.fromName }} + {{ relation.fromName | customTranslate }} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/action-rule-node-config.module.ts b/ui-ngx/src/app/modules/home/components/rule-node/action/action-rule-node-config.module.ts new file mode 100644 index 0000000000..e71055b5da --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/action-rule-node-config.module.ts @@ -0,0 +1,113 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@shared/public-api'; +import { HomeComponentsModule } from '@home/components/public-api'; +import { AttributesConfigComponent } from './attributes-config.component'; +import { TimeseriesConfigComponent } from './timeseries-config.component'; +import { RpcRequestConfigComponent } from './rpc-request-config.component'; +import { LogConfigComponent } from './log-config.component'; +import { AssignCustomerConfigComponent } from './assign-customer-config.component'; +import { ClearAlarmConfigComponent } from './clear-alarm-config.component'; +import { CreateAlarmConfigComponent } from './create-alarm-config.component'; +import { CreateRelationConfigComponent } from './create-relation-config.component'; +import { MsgDelayConfigComponent } from './msg-delay-config.component'; +import { DeleteRelationConfigComponent } from './delete-relation-config.component'; +import { GeneratorConfigComponent } from './generator-config.component'; +import { GpsGeoActionConfigComponent } from './gps-geo-action-config.component'; +import { MsgCountConfigComponent } from './msg-count-config.component'; +import { RpcReplyConfigComponent } from './rpc-reply-config.component'; +import { SaveToCustomTableConfigComponent } from './save-to-custom-table-config.component'; +import { CommonRuleNodeConfigModule } from '../common/common-rule-node-config.module'; +import { UnassignCustomerConfigComponent } from './unassign-customer-config.component'; +import { DeviceProfileConfigComponent } from './device-profile-config.component'; +import { PushToEdgeConfigComponent } from './push-to-edge-config.component'; +import { PushToCloudConfigComponent } from './push-to-cloud-config.component'; +import { DeleteAttributesConfigComponent } from './delete-attributes-config.component'; +import { MathFunctionConfigComponent } from './math-function-config.component'; +import { DeviceStateConfigComponent } from './device-state-config.component'; +import { SendRestApiCallReplyConfigComponent } from './send-rest-api-call-reply-config.component'; +import { + AdvancedPersistenceSettingComponent +} from '@home/components/rule-node/action/advanced-persistence-setting.component'; +import { + AdvancedPersistenceSettingRowComponent +} from '@home/components/rule-node/action/advanced-persistence-setting-row.component'; + +@NgModule({ + declarations: [ + DeleteAttributesConfigComponent, + AttributesConfigComponent, + TimeseriesConfigComponent, + RpcRequestConfigComponent, + LogConfigComponent, + AssignCustomerConfigComponent, + ClearAlarmConfigComponent, + CreateAlarmConfigComponent, + CreateRelationConfigComponent, + MsgDelayConfigComponent, + DeleteRelationConfigComponent, + GeneratorConfigComponent, + GpsGeoActionConfigComponent, + MsgCountConfigComponent, + RpcReplyConfigComponent, + SaveToCustomTableConfigComponent, + UnassignCustomerConfigComponent, + SendRestApiCallReplyConfigComponent, + DeviceProfileConfigComponent, + PushToEdgeConfigComponent, + PushToCloudConfigComponent, + MathFunctionConfigComponent, + DeviceStateConfigComponent, + AdvancedPersistenceSettingComponent, + AdvancedPersistenceSettingRowComponent, + ], + imports: [ + CommonModule, + SharedModule, + HomeComponentsModule, + CommonRuleNodeConfigModule + ], + exports: [ + DeleteAttributesConfigComponent, + AttributesConfigComponent, + TimeseriesConfigComponent, + RpcRequestConfigComponent, + LogConfigComponent, + AssignCustomerConfigComponent, + ClearAlarmConfigComponent, + CreateAlarmConfigComponent, + CreateRelationConfigComponent, + MsgDelayConfigComponent, + DeleteRelationConfigComponent, + GeneratorConfigComponent, + GpsGeoActionConfigComponent, + MsgCountConfigComponent, + RpcReplyConfigComponent, + SaveToCustomTableConfigComponent, + UnassignCustomerConfigComponent, + SendRestApiCallReplyConfigComponent, + DeviceProfileConfigComponent, + PushToEdgeConfigComponent, + PushToCloudConfigComponent, + MathFunctionConfigComponent, + DeviceStateConfigComponent + ] +}) +export class ActionRuleNodeConfigModule { +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/advanced-persistence-setting-row.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/advanced-persistence-setting-row.component.html new file mode 100644 index 0000000000..2bb51ad504 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/advanced-persistence-setting-row.component.html @@ -0,0 +1,40 @@ + +
    +
    {{ title }}
    + + rule-node-config.save-time-series.strategy + + @for (strategy of persistenceStrategies; track strategy) { + {{ PersistenceTypeTranslationMap.get(strategy) | translate }} + } + + + @if(persistenceSettingRowForm.get('type').value === PersistenceType.DEDUPLICATE) { + + + } +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/advanced-persistence-setting-row.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/action/advanced-persistence-setting-row.component.ts new file mode 100644 index 0000000000..bf9b48aca3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/advanced-persistence-setting-row.component.ts @@ -0,0 +1,114 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input } from '@angular/core'; +import { + ControlValueAccessor, + FormBuilder, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + ValidationErrors, + Validator +} from '@angular/forms'; +import { + AdvancedProcessingConfig, + defaultAdvancedProcessingConfig, + maxDeduplicateTimeSecs, + ProcessingType, + ProcessingTypeTranslationMap +} from '@home/components/rule-node/action/timeseries-config.models'; +import { isDefinedAndNotNull } from '@core/utils'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; + +@Component({ + selector: 'tb-advanced-persistence-setting-row', + templateUrl: './advanced-persistence-setting-row.component.html', + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => AdvancedPersistenceSettingRowComponent), + multi: true + },{ + provide: NG_VALIDATORS, + useExisting: forwardRef(() => AdvancedPersistenceSettingRowComponent), + multi: true + }] +}) +export class AdvancedPersistenceSettingRowComponent implements ControlValueAccessor, Validator { + + @Input() + title: string; + + persistenceSettingRowForm = this.fb.group({ + type: [defaultAdvancedProcessingConfig.type], + deduplicationIntervalSecs: [{value: 60, disabled: true}] + }); + + PersistenceType = ProcessingType; + persistenceStrategies = [ProcessingType.ON_EVERY_MESSAGE, ProcessingType.DEDUPLICATE, ProcessingType.SKIP]; + PersistenceTypeTranslationMap = ProcessingTypeTranslationMap; + + maxDeduplicateTime = maxDeduplicateTimeSecs; + + private propagateChange: (value: any) => void = () => {}; + + constructor(private fb: FormBuilder) { + this.persistenceSettingRowForm.get('type').valueChanges.pipe( + takeUntilDestroyed() + ).subscribe(() => this.updatedValidation()); + + this.persistenceSettingRowForm.valueChanges.pipe( + takeUntilDestroyed() + ).subscribe((value) => this.propagateChange(value)); + } + + registerOnChange(fn: any) { + this.propagateChange = fn; + } + + registerOnTouched(_fn: any) { + } + + setDisabledState(isDisabled: boolean) { + if (isDisabled) { + this.persistenceSettingRowForm.disable({emitEvent: false}); + } else { + this.persistenceSettingRowForm.enable({emitEvent: false}); + this.updatedValidation(); + } + } + + validate(): ValidationErrors | null { + return this.persistenceSettingRowForm.valid ? null : { + persistenceSettingRow: false + }; + } + + writeValue(value: AdvancedProcessingConfig) { + if (isDefinedAndNotNull(value)) { + this.persistenceSettingRowForm.patchValue(value, {emitEvent: false}); + } else { + this.persistenceSettingRowForm.patchValue(defaultAdvancedProcessingConfig); + } + } + + private updatedValidation() { + if (this.persistenceSettingRowForm.get('type').value === ProcessingType.DEDUPLICATE) { + this.persistenceSettingRowForm.get('deduplicationIntervalSecs').enable({emitEvent: false}); + } else { + this.persistenceSettingRowForm.get('deduplicationIntervalSecs').disable({emitEvent: false}) + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/advanced-persistence-setting.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/advanced-persistence-setting.component.html new file mode 100644 index 0000000000..d3cbb1cdcb --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/advanced-persistence-setting.component.html @@ -0,0 +1,36 @@ + +
    + + + + + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/advanced-persistence-setting.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/action/advanced-persistence-setting.component.ts new file mode 100644 index 0000000000..969f432f5f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/advanced-persistence-setting.component.ts @@ -0,0 +1,82 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + ControlValueAccessor, + FormBuilder, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + ValidationErrors, + Validator +} from '@angular/forms'; +import { Component, forwardRef } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { AdvancedProcessingStrategy } from '@home/components/rule-node/action/timeseries-config.models'; + +@Component({ + selector: 'tb-advanced-persistence-settings', + templateUrl: './advanced-persistence-setting.component.html', + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => AdvancedPersistenceSettingComponent), + multi: true + },{ + provide: NG_VALIDATORS, + useExisting: forwardRef(() => AdvancedPersistenceSettingComponent), + multi: true + }] +}) +export class AdvancedPersistenceSettingComponent implements ControlValueAccessor, Validator { + + persistenceForm = this.fb.group({ + timeseries: [null], + latest: [null], + webSockets: [null] + }); + + private propagateChange: (value: any) => void = () => {}; + + constructor(private fb: FormBuilder) { + this.persistenceForm.valueChanges.pipe( + takeUntilDestroyed() + ).subscribe(value => this.propagateChange(value)); + } + + registerOnChange(fn: any) { + this.propagateChange = fn; + } + + registerOnTouched(_fn: any) { + } + + setDisabledState(isDisabled: boolean) { + if (isDisabled) { + this.persistenceForm.disable({emitEvent: false}); + } else { + this.persistenceForm.enable({emitEvent: false}); + } + } + + validate(): ValidationErrors | null { + return this.persistenceForm.valid ? null : { + persistenceForm: false + }; + } + + writeValue(value: AdvancedProcessingStrategy) { + this.persistenceForm.patchValue(value, {emitEvent: false}); + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/assign-customer-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/assign-customer-config.component.html new file mode 100644 index 0000000000..4ec27d83e7 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/assign-customer-config.component.html @@ -0,0 +1,35 @@ + +
    +
    + + rule-node-config.customer-name-pattern + + + {{ 'rule-node-config.customer-name-pattern-required' | translate }} + + rule-node-config.customer-name-pattern-hint + +
    + + {{ 'rule-node-config.create-customer-if-not-exists' | translate }} + +
    +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/assign-customer-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/action/assign-customer-config.component.ts new file mode 100644 index 0000000000..924cb448e3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/assign-customer-config.component.ts @@ -0,0 +1,49 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; + +@Component({ + selector: 'tb-action-node-assign-to-customer-config', + templateUrl: './assign-customer-config.component.html', + styleUrls: [] +}) +export class AssignCustomerConfigComponent extends RuleNodeConfigurationComponent { + + assignCustomerConfigForm: UntypedFormGroup; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.assignCustomerConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.assignCustomerConfigForm = this.fb.group({ + customerNamePattern: [configuration ? configuration.customerNamePattern : null, [Validators.required, Validators.pattern(/.*\S.*/)]], + createCustomerIfNotExists: [configuration ? configuration.createCustomerIfNotExists : false, []] + }); + } + + protected prepareOutputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + configuration.customerNamePattern = configuration.customerNamePattern.trim(); + return configuration; + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/attributes-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/attributes-config.component.html new file mode 100644 index 0000000000..258f856e91 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/attributes-config.component.html @@ -0,0 +1,79 @@ + +
    +
    + + +
    + + {{ 'rule-node-config.attributes-scope' | translate }} + + + {{ telemetryTypeTranslationsMap.get(scope) | translate }} + + + + + {{ 'rule-node-config.attributes-scope-value' | translate }} + + + +
    +
    + +
    + + + rule-node-config.advanced-settings + +
    + + {{ 'rule-node-config.update-attributes-only-on-value-change' | translate }} + +
    +
    + + {{ 'rule-node-config.send-attributes-updated-notification' | translate }} + +
    +
    + + {{ 'rule-node-config.notify-device' | translate }} + +
    +
    +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/attributes-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/action/attributes-config.component.ts new file mode 100644 index 0000000000..29b5dd2bc5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/attributes-config.component.ts @@ -0,0 +1,65 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; +import { AttributeScope, telemetryTypeTranslations } from '@app/shared/models/telemetry/telemetry.models'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; + +@Component({ + selector: 'tb-action-node-attributes-config', + templateUrl: './attributes-config.component.html', + styleUrls: [] +}) +export class AttributesConfigComponent extends RuleNodeConfigurationComponent { + + attributeScopeMap = AttributeScope; + attributeScopes = Object.keys(AttributeScope); + telemetryTypeTranslationsMap = telemetryTypeTranslations; + + attributesConfigForm: UntypedFormGroup; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.attributesConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.attributesConfigForm = this.fb.group({ + scope: [configuration ? configuration.scope : null, [Validators.required]], + notifyDevice: [configuration ? configuration.notifyDevice : true, []], + sendAttributesUpdatedNotification: [configuration ? configuration.sendAttributesUpdatedNotification : false, []], + updateAttributesOnlyOnValueChange: [configuration ? configuration.updateAttributesOnlyOnValueChange : false, []] + }); + + this.attributesConfigForm.get('scope').valueChanges.pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe((value) => { + if (value !== AttributeScope.SHARED_SCOPE) { + this.attributesConfigForm.get('notifyDevice').patchValue(false, {emitEvent: false}); + } + if (value === AttributeScope.CLIENT_SCOPE) { + this.attributesConfigForm.get('sendAttributesUpdatedNotification').patchValue(false, {emitEvent: false}); + } + this.attributesConfigForm.get('updateAttributesOnlyOnValueChange').patchValue(false, {emitEvent: false}); + }); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/clear-alarm-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/clear-alarm-config.component.html new file mode 100644 index 0000000000..075978ce69 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/clear-alarm-config.component.html @@ -0,0 +1,67 @@ + +
    + + + + + + + +
    + +
    + + rule-node-config.alarm-type + + + {{ 'rule-node-config.alarm-type-required' | translate }} + + rule-node-config.general-pattern-hint + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/clear-alarm-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/action/clear-alarm-config.component.ts new file mode 100644 index 0000000000..cf96982621 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/clear-alarm-config.component.ts @@ -0,0 +1,127 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, EventEmitter, ViewChild } from '@angular/core'; +import { AppState, getCurrentAuthState, NodeScriptTestService } from '@core/public-api'; +import { Store } from '@ngrx/store'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; +import { + RuleNodeConfiguration, + RuleNodeConfigurationComponent, + ScriptLanguage +} from '@app/shared/models/rule-node.models'; +import type { JsFuncComponent } from '@app/shared/components/js-func.component'; +import { DebugRuleNodeEventBody } from '@shared/models/event.models'; + +@Component({ + selector: 'tb-action-node-clear-alarm-config', + templateUrl: './clear-alarm-config.component.html', + styleUrls: [] +}) +export class ClearAlarmConfigComponent extends RuleNodeConfigurationComponent { + + @ViewChild('jsFuncComponent', {static: false}) jsFuncComponent: JsFuncComponent; + @ViewChild('tbelFuncComponent', {static: false}) tbelFuncComponent: JsFuncComponent; + + clearAlarmConfigForm: UntypedFormGroup; + + tbelEnabled = getCurrentAuthState(this.store).tbelEnabled; + + scriptLanguage = ScriptLanguage; + + changeScript: EventEmitter = new EventEmitter(); + + readonly hasScript = true; + + readonly testScriptLabel = 'rule-node-config.test-details-function'; + + constructor(protected store: Store, + private fb: UntypedFormBuilder, + private nodeScriptTestService: NodeScriptTestService, + private translate: TranslateService) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.clearAlarmConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.clearAlarmConfigForm = this.fb.group({ + scriptLang: [configuration ? configuration.scriptLang : ScriptLanguage.JS, [Validators.required]], + alarmDetailsBuildJs: [configuration ? configuration.alarmDetailsBuildJs : null, []], + alarmDetailsBuildTbel: [configuration ? configuration.alarmDetailsBuildTbel : null, []], + alarmType: [configuration ? configuration.alarmType : null, [Validators.required]] + }); + } + + protected validatorTriggers(): string[] { + return ['scriptLang']; + } + + protected updateValidators(emitEvent: boolean) { + let scriptLang: ScriptLanguage = this.clearAlarmConfigForm.get('scriptLang').value; + if (scriptLang === ScriptLanguage.TBEL && !this.tbelEnabled) { + scriptLang = ScriptLanguage.JS; + this.clearAlarmConfigForm.get('scriptLang').patchValue(scriptLang, {emitEvent: false}); + setTimeout(() => {this.clearAlarmConfigForm.updateValueAndValidity({emitEvent: true});}); + } + this.clearAlarmConfigForm.get('alarmDetailsBuildJs').setValidators(scriptLang === ScriptLanguage.JS ? [Validators.required] : []); + this.clearAlarmConfigForm.get('alarmDetailsBuildJs').updateValueAndValidity({emitEvent}); + this.clearAlarmConfigForm.get('alarmDetailsBuildTbel').setValidators(scriptLang === ScriptLanguage.TBEL ? [Validators.required] : []); + this.clearAlarmConfigForm.get('alarmDetailsBuildTbel').updateValueAndValidity({emitEvent}); + } + + protected prepareInputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + if (configuration) { + if (!configuration.scriptLang) { + configuration.scriptLang = ScriptLanguage.JS; + } + } + return configuration; + } + + testScript(debugEventBody?: DebugRuleNodeEventBody) { + const scriptLang: ScriptLanguage = this.clearAlarmConfigForm.get('scriptLang').value; + const scriptField = scriptLang === ScriptLanguage.JS ? 'alarmDetailsBuildJs' : 'alarmDetailsBuildTbel'; + const helpId = scriptLang === ScriptLanguage.JS ? 'rulenode/clear_alarm_node_script_fn' : 'rulenode/tbel/clear_alarm_node_script_fn'; + const script: string = this.clearAlarmConfigForm.get(scriptField).value; + this.nodeScriptTestService.testNodeScript( + script, + 'json', + this.translate.instant('rule-node-config.details'), + 'Details', + ['msg', 'metadata', 'msgType'], + this.ruleNodeId, + helpId, + scriptLang, + debugEventBody + ).subscribe((theScript) => { + if (theScript) { + this.clearAlarmConfigForm.get(scriptField).setValue(theScript); + this.changeScript.emit(); + } + }); + } + + protected onValidate() { + const scriptLang: ScriptLanguage = this.clearAlarmConfigForm.get('scriptLang').value; + if (scriptLang === ScriptLanguage.JS) { + this.jsFuncComponent.validateOnSubmit(); + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/create-alarm-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/create-alarm-config.component.html new file mode 100644 index 0000000000..9ec4238081 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/create-alarm-config.component.html @@ -0,0 +1,128 @@ + +
    + + {{ 'rule-node-config.use-message-alarm-data' | translate }} + + + {{ 'rule-node-config.overwrite-alarm-details' | translate }} + +
    + + + + + + + +
    + +
    +
    +
    + + rule-node-config.alarm-type + + + {{ 'rule-node-config.alarm-type-required' | translate }} + + rule-node-config.general-pattern-hint + + + {{ 'rule-node-config.use-alarm-severity-pattern' | translate }} + + + rule-node-config.alarm-severity + + + {{ alarmSeverityTranslationMap.get(severity) | translate }} + + + + {{ 'rule-node-config.alarm-severity-required' | translate }} + + + + rule-node-config.alarm-severity-pattern + + + {{ 'rule-node-config.alarm-severity-required' | translate }} + + + + + {{ 'rule-node-config.propagate' | translate }} + +
    + + rule-node-config.relation-types-list + + + {{key}} + close + + + + rule-node-config.relation-types-list-hint + +
    + + {{ 'rule-node-config.propagate-to-owner' | translate }} + + + {{ 'rule-node-config.propagate-to-tenant' | translate }} + +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/create-alarm-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/action/create-alarm-config.component.ts new file mode 100644 index 0000000000..738ec589be --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/create-alarm-config.component.ts @@ -0,0 +1,202 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, EventEmitter, ViewChild } from '@angular/core'; +import { AppState, getCurrentAuthState, NodeScriptTestService } from '@core/public-api'; +import { Store } from '@ngrx/store'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; +import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes'; +import { MatChipInputEvent } from '@angular/material/chips'; +import { + RuleNodeConfiguration, + RuleNodeConfigurationComponent, + ScriptLanguage +} from '@app/shared/models/rule-node.models'; +import type { JsFuncComponent } from '@app/shared/components/js-func.component'; +import { AlarmSeverity, alarmSeverityTranslations } from '@app/shared/models/alarm.models'; +import { DebugRuleNodeEventBody } from '@shared/models/event.models'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; + +@Component({ + selector: 'tb-action-node-create-alarm-config', + templateUrl: './create-alarm-config.component.html', + styleUrls: [] +}) +export class CreateAlarmConfigComponent extends RuleNodeConfigurationComponent { + + @ViewChild('jsFuncComponent', {static: false}) jsFuncComponent: JsFuncComponent; + @ViewChild('tbelFuncComponent', {static: false}) tbelFuncComponent: JsFuncComponent; + + alarmSeverities = Object.keys(AlarmSeverity); + alarmSeverityTranslationMap = alarmSeverityTranslations; + createAlarmConfigForm: UntypedFormGroup; + + separatorKeysCodes = [ENTER, COMMA, SEMICOLON]; + + tbelEnabled = getCurrentAuthState(this.store).tbelEnabled; + + scriptLanguage = ScriptLanguage; + + changeScript: EventEmitter = new EventEmitter(); + + readonly hasScript = true; + + readonly testScriptLabel = 'rule-node-config.test-details-function'; + + constructor(protected store: Store, + private fb: UntypedFormBuilder, + private nodeScriptTestService: NodeScriptTestService, + private translate: TranslateService) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.createAlarmConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.createAlarmConfigForm = this.fb.group({ + scriptLang: [configuration ? configuration.scriptLang : ScriptLanguage.JS, [Validators.required]], + alarmDetailsBuildJs: [configuration ? configuration.alarmDetailsBuildJs : null, []], + alarmDetailsBuildTbel: [configuration ? configuration.alarmDetailsBuildTbel : null, []], + useMessageAlarmData: [configuration ? configuration.useMessageAlarmData : false, []], + overwriteAlarmDetails: [configuration ? configuration.overwriteAlarmDetails : false, []], + alarmType: [configuration ? configuration.alarmType : null, []], + severity: [configuration ? configuration.severity : null, []], + propagate: [configuration ? configuration.propagate : false, []], + relationTypes: [configuration ? configuration.relationTypes : null, []], + propagateToOwner: [configuration ? configuration.propagateToOwner : false, []], + propagateToTenant: [configuration ? configuration.propagateToTenant : false, []], + dynamicSeverity: false + }); + + this.createAlarmConfigForm.get('dynamicSeverity').valueChanges.pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe((dynamicSeverity) => { + if(dynamicSeverity){ + this.createAlarmConfigForm.get('severity').patchValue('',{emitEvent:false}); + } else { + this.createAlarmConfigForm.get('severity').patchValue(this.alarmSeverities[0],{emitEvent:false}); + } + }); + + } + + + protected validatorTriggers(): string[] { + return ['useMessageAlarmData', 'overwriteAlarmDetails', 'scriptLang']; + } + + protected updateValidators(emitEvent: boolean) { + const useMessageAlarmData: boolean = this.createAlarmConfigForm.get('useMessageAlarmData').value; + const overwriteAlarmDetails: boolean = this.createAlarmConfigForm.get('overwriteAlarmDetails').value; + if (useMessageAlarmData) { + this.createAlarmConfigForm.get('alarmType').setValidators([]); + this.createAlarmConfigForm.get('severity').setValidators([]); + } else { + this.createAlarmConfigForm.get('alarmType').setValidators([Validators.required]); + this.createAlarmConfigForm.get('severity').setValidators([Validators.required]); + } + this.createAlarmConfigForm.get('alarmType').updateValueAndValidity({emitEvent}); + this.createAlarmConfigForm.get('severity').updateValueAndValidity({emitEvent}); + + let scriptLang: ScriptLanguage = this.createAlarmConfigForm.get('scriptLang').value; + if (scriptLang === ScriptLanguage.TBEL && !this.tbelEnabled) { + scriptLang = ScriptLanguage.JS; + this.createAlarmConfigForm.get('scriptLang').patchValue(scriptLang, {emitEvent: false}); + setTimeout(() => {this.createAlarmConfigForm.updateValueAndValidity({emitEvent: true});}); + } + const useAlarmDetailsBuildScript = useMessageAlarmData === false || overwriteAlarmDetails === true; + this.createAlarmConfigForm.get('alarmDetailsBuildJs') + .setValidators(useAlarmDetailsBuildScript && scriptLang === ScriptLanguage.JS ? [Validators.required] : []); + this.createAlarmConfigForm.get('alarmDetailsBuildTbel') + .setValidators(useAlarmDetailsBuildScript && scriptLang === ScriptLanguage.TBEL ? [Validators.required] : []); + this.createAlarmConfigForm.get('alarmDetailsBuildJs').updateValueAndValidity({emitEvent}); + this.createAlarmConfigForm.get('alarmDetailsBuildTbel').updateValueAndValidity({emitEvent}); + } + + protected prepareInputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + if (configuration) { + if (!configuration.scriptLang) { + configuration.scriptLang = ScriptLanguage.JS; + } + } + return configuration; + } + + testScript(debugEventBody?: DebugRuleNodeEventBody) { + const scriptLang: ScriptLanguage = this.createAlarmConfigForm.get('scriptLang').value; + const scriptField = scriptLang === ScriptLanguage.JS ? 'alarmDetailsBuildJs' : 'alarmDetailsBuildTbel'; + const helpId = scriptLang === ScriptLanguage.JS ? 'rulenode/create_alarm_node_script_fn' : 'rulenode/tbel/create_alarm_node_script_fn'; + const script: string = this.createAlarmConfigForm.get(scriptField).value; + this.nodeScriptTestService.testNodeScript( + script, + 'json', + this.translate.instant('rule-node-config.details'), + 'Details', + ['msg', 'metadata', 'msgType'], + this.ruleNodeId, + helpId, + scriptLang, + debugEventBody + ).subscribe((theScript) => { + if (theScript) { + this.createAlarmConfigForm.get(scriptField).setValue(theScript); + this.changeScript.emit(); + } + }); + } + + removeKey(key: string, keysField: string): void { + const keys: string[] = this.createAlarmConfigForm.get(keysField).value; + const index = keys.indexOf(key); + if (index >= 0) { + keys.splice(index, 1); + this.createAlarmConfigForm.get(keysField).setValue(keys, {emitEvent: true}); + } + } + + addKey(event: MatChipInputEvent, keysField: string): void { + const input = event.input; + let value = event.value; + if ((value || '').trim()) { + value = value.trim(); + let keys: string[] = this.createAlarmConfigForm.get(keysField).value; + if (!keys || keys.indexOf(value) === -1) { + if (!keys) { + keys = []; + } + keys.push(value); + this.createAlarmConfigForm.get(keysField).setValue(keys, {emitEvent: true}); + } + } + if (input) { + input.value = ''; + } + } + + protected onValidate() { + const useMessageAlarmData: boolean = this.createAlarmConfigForm.get('useMessageAlarmData').value; + const overwriteAlarmDetails: boolean = this.createAlarmConfigForm.get('overwriteAlarmDetails').value; + if (!useMessageAlarmData || overwriteAlarmDetails) { + const scriptLang: ScriptLanguage = this.createAlarmConfigForm.get('scriptLang').value; + if (scriptLang === ScriptLanguage.JS) { + this.jsFuncComponent.validateOnSubmit(); + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/create-relation-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/create-relation-config.component.html new file mode 100644 index 0000000000..362bb4d9b6 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/create-relation-config.component.html @@ -0,0 +1,100 @@ + +
    +
    +
    rule-node-config.relation-parameters
    +
    + + relation.direction + + + {{ directionTypeTranslations.get(type) | translate }} + + + + + +
    +
    + +
    +
    rule-node-config.target-entity
    +
    + + + + + {{ entityTypeNamePatternTranslation.get(createRelationConfigForm.get('entityType').value) | translate }} + + + + + rule-node-config.profile-name + + +
    + + + +
    + + {{ 'rule-node-config.create-entity-if-not-exists' | translate }} + +
    +
    +
    + + + rule-node-config.advanced-settings + +
    +
    + + {{ 'rule-node-config.remove-current-relations' | translate }} + +
    +
    + + {{ 'rule-node-config.change-originator-to-related-entity' | translate }} + +
    +
    +
    +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/create-relation-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/action/create-relation-config.component.ts new file mode 100644 index 0000000000..b48a530b57 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/create-relation-config.component.ts @@ -0,0 +1,107 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@app/shared/models/rule-node.models'; +import { EntitySearchDirection } from '@app/shared/models/relation.models'; +import { EntityType } from '@app/shared/models/entity-type.models'; + +@Component({ + selector: 'tb-action-node-create-relation-config', + templateUrl: './create-relation-config.component.html', + styleUrls: [] +}) +export class CreateRelationConfigComponent extends RuleNodeConfigurationComponent { + + directionTypes = Object.keys(EntitySearchDirection); + directionTypeTranslations = new Map( + [ + [EntitySearchDirection.FROM, 'rule-node-config.search-direction-from'], + [EntitySearchDirection.TO, 'rule-node-config.search-direction-to'], + ] + ); + + entityType = EntityType; + + entityTypeNamePatternTranslation = new Map( + [ + [EntityType.DEVICE, 'rule-node-config.device-name-pattern'], + [EntityType.ASSET, 'rule-node-config.asset-name-pattern'], + [EntityType.ENTITY_VIEW, 'rule-node-config.entity-view-name-pattern'], + [EntityType.CUSTOMER, 'rule-node-config.customer-title-pattern'], + [EntityType.USER, 'rule-node-config.user-name-pattern'], + [EntityType.DASHBOARD, 'rule-node-config.dashboard-name-pattern'], + [EntityType.EDGE, 'rule-node-config.edge-name-pattern'] + ] + ); + + allowedEntityTypes = [EntityType.DEVICE, EntityType.ASSET, EntityType.ENTITY_VIEW, EntityType.TENANT, + EntityType.CUSTOMER, EntityType.USER, EntityType.DASHBOARD, EntityType.EDGE]; + + createRelationConfigForm: UntypedFormGroup; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.createRelationConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.createRelationConfigForm = this.fb.group({ + direction: [configuration ? configuration.direction : null, [Validators.required]], + entityType: [configuration ? configuration.entityType : null, [Validators.required]], + entityNamePattern: [configuration ? configuration.entityNamePattern : null, []], + entityTypePattern: [configuration ? configuration.entityTypePattern : null, []], + relationType: [configuration ? configuration.relationType : null, [Validators.required]], + createEntityIfNotExists: [configuration ? configuration.createEntityIfNotExists : false, []], + removeCurrentRelations: [configuration ? configuration.removeCurrentRelations : false, []], + changeOriginatorToRelatedEntity: [configuration ? configuration.changeOriginatorToRelatedEntity : false, []] + }); + } + + protected validatorTriggers(): string[] { + return ['entityType', 'createEntityIfNotExists']; + } + + protected updateValidators(emitEvent: boolean) { + const entityType: EntityType = this.createRelationConfigForm.get('entityType').value; + if (entityType) { + this.createRelationConfigForm.get('entityNamePattern').setValidators([Validators.required, Validators.pattern(/.*\S.*/)]); + } else { + this.createRelationConfigForm.get('entityNamePattern').setValidators([]); + } + if (entityType && (entityType === EntityType.DEVICE || entityType === EntityType.ASSET)) { + const validators = [Validators.pattern(/.*\S.*/)] + if (this.createRelationConfigForm.get('createEntityIfNotExists').value) { + validators.push(Validators.required); + } + this.createRelationConfigForm.get('entityTypePattern').setValidators(validators); + } else { + this.createRelationConfigForm.get('entityTypePattern').setValidators([]); + } + this.createRelationConfigForm.get('entityNamePattern').updateValueAndValidity({emitEvent}); + this.createRelationConfigForm.get('entityTypePattern').updateValueAndValidity({emitEvent}); + } + + protected prepareOutputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + configuration.entityNamePattern = configuration.entityNamePattern ? configuration.entityNamePattern.trim() : null; + configuration.entityTypePattern = configuration.entityTypePattern ? configuration.entityTypePattern.trim() : null; + return configuration; + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/delete-attributes-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/delete-attributes-config.component.html new file mode 100644 index 0000000000..0f62bec757 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/delete-attributes-config.component.html @@ -0,0 +1,89 @@ + +
    +
    + + +
    + + {{ 'rule-node-config.attributes-scope' | translate }} + + + {{ telemetryTypeTranslationsMap.get(scope) | translate }} + + + + + {{ 'rule-node-config.attributes-scope-value' | translate }} + + + +
    +
    + + + {{ 'rule-node-config.attributes-keys' | translate }} + + + {{key}} + close + + + + {{ 'rule-node-config.attributes-keys-required' | translate }} + rule-node-config.general-pattern-hint + + +
    + + + rule-node-config.advanced-settings + +
    + + {{ 'rule-node-config.send-attributes-deleted-notification' | translate }} + +
    +
    + + {{ 'rule-node-config.notify-device' | translate }} + +
    +
    +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/delete-attributes-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/action/delete-attributes-config.component.ts new file mode 100644 index 0000000000..b61f9aa7c1 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/delete-attributes-config.component.ts @@ -0,0 +1,91 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, ViewChild } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { MatChipGrid, MatChipInputEvent } from '@angular/material/chips'; +import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@app/shared/models/rule-node.models'; +import { AttributeScope, telemetryTypeTranslations } from '@shared/models/telemetry/telemetry.models'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; + +@Component({ + selector: 'tb-action-node-delete-attributes-config', + templateUrl: './delete-attributes-config.component.html', + styleUrls: [] +}) +export class DeleteAttributesConfigComponent extends RuleNodeConfigurationComponent { + @ViewChild('attributeChipList') attributeChipList: MatChipGrid; + + deleteAttributesConfigForm: UntypedFormGroup; + attributeScopeMap = AttributeScope; + attributeScopes = Object.keys(AttributeScope); + telemetryTypeTranslationsMap = telemetryTypeTranslations; + separatorKeysCodes = [ENTER, COMMA, SEMICOLON]; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.deleteAttributesConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.deleteAttributesConfigForm = this.fb.group({ + scope: [configuration ? configuration.scope : null, [Validators.required]], + keys: [configuration ? configuration.keys : null, [Validators.required]], + sendAttributesDeletedNotification: [configuration ? configuration.sendAttributesDeletedNotification : false, []], + notifyDevice: [configuration ? configuration.notifyDevice : false, []] + }); + + this.deleteAttributesConfigForm.get('scope').valueChanges.pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe((value) => { + if (value !== AttributeScope.SHARED_SCOPE) { + this.deleteAttributesConfigForm.get('notifyDevice').patchValue(false, {emitEvent: false}); + } + }); + } + + removeKey(key: string): void { + const keys: string[] = this.deleteAttributesConfigForm.get('keys').value; + const index = keys.indexOf(key); + if (index >= 0) { + keys.splice(index, 1); + this.deleteAttributesConfigForm.get('keys').patchValue(keys, {emitEvent: true}); + } + } + + addKey(event: MatChipInputEvent): void { + const input = event.input; + let value = event.value; + if ((value || '').trim()) { + value = value.trim(); + let keys: string[] = this.deleteAttributesConfigForm.get('keys').value; + if (!keys || keys.indexOf(value) === -1) { + if (!keys) { + keys = []; + } + keys.push(value); + this.deleteAttributesConfigForm.get('keys').patchValue(keys, {emitEvent: true}); + } + } + if (input) { + input.value = ''; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/delete-relation-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/delete-relation-config.component.html new file mode 100644 index 0000000000..3270f95a53 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/delete-relation-config.component.html @@ -0,0 +1,62 @@ + +
    +
    +
    rule-node-config.relation-parameters
    +
    + + relation.direction + + + {{ directionTypeTranslations.get(type) | translate }} + + + + + +
    +
    +
    +
    + + {{ 'rule-node-config.delete-relation-with-specific-entity' | translate }} + +
    +
    +
    + + + + {{ entityTypeNamePatternTranslation.get(deleteRelationConfigForm.get('entityType').value) | translate }} + + +
    + +
    +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/delete-relation-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/action/delete-relation-config.component.ts new file mode 100644 index 0000000000..34337f95ad --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/delete-relation-config.component.ts @@ -0,0 +1,101 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { EntityType } from '@app/shared/models/entity-type.models'; +import { EntitySearchDirection } from '@app/shared/models/relation.models'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@app/shared/models/rule-node.models'; + +@Component({ + selector: 'tb-action-node-delete-relation-config', + templateUrl: './delete-relation-config.component.html', + styleUrls: [] +}) +export class DeleteRelationConfigComponent extends RuleNodeConfigurationComponent { + + directionTypes = Object.keys(EntitySearchDirection); + + directionTypeTranslations = new Map( + [ + [EntitySearchDirection.FROM, 'rule-node-config.del-relation-direction-from'], + [EntitySearchDirection.TO, 'rule-node-config.del-relation-direction-to'], + ] + ); + + entityTypeNamePatternTranslation = new Map( + [ + [EntityType.DEVICE, 'rule-node-config.device-name-pattern'], + [EntityType.ASSET, 'rule-node-config.asset-name-pattern'], + [EntityType.ENTITY_VIEW, 'rule-node-config.entity-view-name-pattern'], + [EntityType.CUSTOMER, 'rule-node-config.customer-title-pattern'], + [EntityType.USER, 'rule-node-config.user-name-pattern'], + [EntityType.DASHBOARD, 'rule-node-config.dashboard-name-pattern'], + [EntityType.EDGE, 'rule-node-config.edge-name-pattern'] + ] + ); + + entityType = EntityType; + + allowedEntityTypes = [EntityType.DEVICE, EntityType.ASSET, EntityType.ENTITY_VIEW, EntityType.TENANT, + EntityType.CUSTOMER, EntityType.USER, EntityType.DASHBOARD, EntityType.EDGE]; + + deleteRelationConfigForm: UntypedFormGroup; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.deleteRelationConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.deleteRelationConfigForm = this.fb.group({ + deleteForSingleEntity: [configuration ? configuration.deleteForSingleEntity : false, []], + direction: [configuration ? configuration.direction : null, [Validators.required]], + entityType: [configuration ? configuration.entityType : null, []], + entityNamePattern: [configuration ? configuration.entityNamePattern : null, []], + relationType: [configuration ? configuration.relationType : null, [Validators.required]] + }); + } + + protected validatorTriggers(): string[] { + return ['deleteForSingleEntity', 'entityType']; + } + + protected updateValidators(emitEvent: boolean) { + const deleteForSingleEntity: boolean = this.deleteRelationConfigForm.get('deleteForSingleEntity').value; + const entityType: EntityType = this.deleteRelationConfigForm.get('entityType').value; + if (deleteForSingleEntity) { + this.deleteRelationConfigForm.get('entityType').setValidators([Validators.required]); + } else { + this.deleteRelationConfigForm.get('entityType').setValidators([]); + } + if (deleteForSingleEntity && entityType && entityType !== EntityType.TENANT) { + this.deleteRelationConfigForm.get('entityNamePattern').setValidators([Validators.required, Validators.pattern(/.*\S.*/)]); + } else { + this.deleteRelationConfigForm.get('entityNamePattern').setValidators([]); + } + this.deleteRelationConfigForm.get('entityType').updateValueAndValidity({emitEvent: false}); + this.deleteRelationConfigForm.get('entityNamePattern').updateValueAndValidity({emitEvent}); + } + + protected prepareOutputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + configuration.entityNamePattern = configuration.entityNamePattern ? configuration.entityNamePattern.trim() : null; + return configuration; + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/device-profile-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/device-profile-config.component.html new file mode 100644 index 0000000000..74594f1400 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/device-profile-config.component.html @@ -0,0 +1,32 @@ + +
    +
    rule-node-config.device-profile-node-hint
    +
    + + {{ 'rule-node-config.persist-alarm-rules' | translate }} + +
    +
    + + {{ 'rule-node-config.fetch-alarm-rules' | translate }} + +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/device-profile-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/action/device-profile-config.component.ts new file mode 100644 index 0000000000..cf02cdf9b1 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/device-profile-config.component.ts @@ -0,0 +1,59 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; + +@Component({ + selector: 'tb-action-node-device-profile-config', + templateUrl: './device-profile-config.component.html', + styleUrls: [] +}) +export class DeviceProfileConfigComponent extends RuleNodeConfigurationComponent { + + deviceProfile: UntypedFormGroup; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.deviceProfile; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.deviceProfile = this.fb.group({ + persistAlarmRulesState: [configuration ? configuration.persistAlarmRulesState : false], + fetchAlarmRulesStateOnStart: [configuration ? configuration.fetchAlarmRulesStateOnStart : false] + }); + } + + protected validatorTriggers(): string[] { + return ['persistAlarmRulesState']; + } + + protected updateValidators(emitEvent: boolean) { + if (this.deviceProfile.get('persistAlarmRulesState').value) { + this.deviceProfile.get('fetchAlarmRulesStateOnStart').enable({emitEvent: false}); + } else { + this.deviceProfile.get('fetchAlarmRulesStateOnStart').setValue(false, {emitEvent: false}); + this.deviceProfile.get('fetchAlarmRulesStateOnStart').disable({emitEvent: false}); + } + this.deviceProfile.get('fetchAlarmRulesStateOnStart').updateValueAndValidity({emitEvent}); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/device-state-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/device-state-config.component.html new file mode 100644 index 0000000000..812c5e9aed --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/device-state-config.component.html @@ -0,0 +1,27 @@ + +
    + + {{ 'rule-node-config.select-device-connectivity-event' | translate }} + + + {{ messageTypeNames.get(eventOption) }} + + + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/device-state-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/action/device-state-config.component.ts new file mode 100644 index 0000000000..75335e0337 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/device-state-config.component.ts @@ -0,0 +1,64 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { isDefinedAndNotNull } from '@core/public-api'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { + MessageType, + messageTypeNames, + RuleNodeConfiguration, + RuleNodeConfigurationComponent +} from '@shared/models/rule-node.models'; + +@Component({ + selector: 'tb-action-node-device-state-config', + templateUrl: './device-state-config.component.html', + styleUrls: [] +}) +export class DeviceStateConfigComponent extends RuleNodeConfigurationComponent { + + deviceState: FormGroup; + + public messageTypeNames = messageTypeNames; + public eventOptions: MessageType[] = [ + MessageType.CONNECT_EVENT, + MessageType.ACTIVITY_EVENT, + MessageType.DISCONNECT_EVENT, + MessageType.INACTIVITY_EVENT + ]; + + constructor(private fb: FormBuilder) { + super(); + } + + protected configForm(): FormGroup { + return this.deviceState; + } + + protected prepareInputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + return { + event: isDefinedAndNotNull(configuration?.event) ? configuration.event : MessageType.ACTIVITY_EVENT + }; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.deviceState = this.fb.group({ + event: [configuration.event, [Validators.required]] + }); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/generator-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/generator-config.component.html new file mode 100644 index 0000000000..cdddbf60da --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/generator-config.component.html @@ -0,0 +1,117 @@ + +
    +
    +
    rule-node-config.generation-parameters
    +
    + + rule-node-config.message-count + + + {{ 'rule-node-config.message-count-required' | translate }} + + + {{ 'rule-node-config.min-message-count-message' | translate }} + + + + rule-node-config.generation-frequency-seconds + + + {{ 'rule-node-config.generation-frequency-required' | translate }} + + + {{ 'rule-node-config.min-generation-frequency-message' | translate }} + + +
    +
    + +
    +
    rule-node-config.originator
    + + +
    +
    + + + rule-node-config.generator-function + + + + + {{ 'rule-node-config.script-lang-tbel' | translate }} + + + {{ 'rule-node-config.script-lang-js' | translate }} + + + + + + + + {{ 'rule-node-config.script-lang-tbel' | translate }} + + + {{ 'rule-node-config.script-lang-js' | translate }} + + + + +
    + +
    +
    +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/generator-config.component.scss b/ui-ngx/src/app/modules/home/components/rule-node/action/generator-config.component.scss new file mode 100644 index 0000000000..93c7934484 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/generator-config.component.scss @@ -0,0 +1,51 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + ::ng-deep { + .mat-button-toggle-group { + min-width: 120px; + height: 24px !important; + .mat-button-toggle { + font-size: 0; + .mat-button-toggle-button { + height: 20px!important; + line-height: 20px !important; + border: none !important; + .mat-button-toggle-label-content { + font-size: 14px !important; + line-height: 20px !important; + } + } + } + } + .tb-entity-select { + @media screen and (min-width: 599px) { + display: flex; + flex-direction: row; + gap: 16px; + } + tb-entity-type-select { + flex: 1; + } + tb-entity-autocomplete { + flex: 1; + mat-form-field { + width: 100% !important; + } + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/generator-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/action/generator-config.component.ts new file mode 100644 index 0000000000..3db74042f7 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/generator-config.component.ts @@ -0,0 +1,155 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, EventEmitter, ViewChild } from '@angular/core'; +import { getCurrentAuthState, isDefinedAndNotNull, NodeScriptTestService } from '@core/public-api'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; +import { + RuleNodeConfiguration, + RuleNodeConfigurationComponent, + ScriptLanguage +} from '@app/shared/models/rule-node.models'; +import type { JsFuncComponent } from '@app/shared/components/js-func.component'; +import { EntityType } from '@app/shared/models/entity-type.models'; +import { DebugRuleNodeEventBody } from '@shared/models/event.models'; + +@Component({ + selector: 'tb-action-node-generator-config', + templateUrl: './generator-config.component.html', + styleUrls: ['generator-config.component.scss'] +}) +export class GeneratorConfigComponent extends RuleNodeConfigurationComponent { + + @ViewChild('jsFuncComponent', {static: false}) jsFuncComponent: JsFuncComponent; + @ViewChild('tbelFuncComponent', {static: false}) tbelFuncComponent: JsFuncComponent; + + generatorConfigForm: UntypedFormGroup; + + tbelEnabled = getCurrentAuthState(this.store).tbelEnabled; + + scriptLanguage = ScriptLanguage; + + changeScript: EventEmitter = new EventEmitter(); + + allowedEntityTypes = [ + EntityType.DEVICE, EntityType.ASSET, EntityType.ENTITY_VIEW, EntityType.CUSTOMER, + EntityType.USER, EntityType.DASHBOARD + ]; + + additionEntityTypes = { + TENANT: this.translate.instant('rule-node-config.current-tenant'), + RULE_NODE: this.translate.instant('rule-node-config.current-rule-node') + }; + + readonly hasScript = true; + + readonly testScriptLabel = 'rule-node-config.test-generator-function'; + + constructor(private fb: UntypedFormBuilder, + private nodeScriptTestService: NodeScriptTestService, + private translate: TranslateService) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.generatorConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.generatorConfigForm = this.fb.group({ + msgCount: [configuration ? configuration.msgCount : null, [Validators.required, Validators.min(0)]], + periodInSeconds: [configuration ? configuration.periodInSeconds : null, [Validators.required, Validators.min(1)]], + originator: [configuration ? configuration.originator : {id: null, entityType: EntityType.RULE_NODE}, []], + scriptLang: [configuration ? configuration.scriptLang : ScriptLanguage.JS, [Validators.required]], + jsScript: [configuration ? configuration.jsScript : null, []], + tbelScript: [configuration ? configuration.tbelScript : null, []] + }); + } + + protected validatorTriggers(): string[] { + return ['scriptLang']; + } + + protected updateValidators(emitEvent: boolean) { + let scriptLang: ScriptLanguage = this.generatorConfigForm.get('scriptLang').value; + if (scriptLang === ScriptLanguage.TBEL && !this.tbelEnabled) { + scriptLang = ScriptLanguage.JS; + this.generatorConfigForm.get('scriptLang').patchValue(scriptLang, {emitEvent: false}); + setTimeout(() => {this.generatorConfigForm.updateValueAndValidity({emitEvent: true});}); + } + this.generatorConfigForm.get('jsScript').setValidators(scriptLang === ScriptLanguage.JS ? [Validators.required] : []); + this.generatorConfigForm.get('jsScript').updateValueAndValidity({emitEvent}); + this.generatorConfigForm.get('tbelScript').setValidators(scriptLang === ScriptLanguage.TBEL ? [Validators.required] : []); + this.generatorConfigForm.get('tbelScript').updateValueAndValidity({emitEvent}); + } + + protected prepareInputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + return { + msgCount: isDefinedAndNotNull(configuration?.msgCount) ? configuration?.msgCount : 0, + periodInSeconds: isDefinedAndNotNull(configuration?.periodInSeconds) ? configuration?.periodInSeconds : 1, + originator: { + id: isDefinedAndNotNull(configuration?.originatorId) ? configuration?.originatorId : null, + entityType: isDefinedAndNotNull(configuration?.originatorType) ? configuration?.originatorType : EntityType.RULE_NODE + }, + scriptLang: isDefinedAndNotNull(configuration?.scriptLang) ? configuration?.scriptLang : ScriptLanguage.JS, + tbelScript: isDefinedAndNotNull(configuration?.tbelScript) ? configuration?.tbelScript : null, + jsScript: isDefinedAndNotNull(configuration?.jsScript) ? configuration?.jsScript : null, + }; + } + + protected prepareOutputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + if (configuration.originator) { + configuration.originatorId = configuration.originator.id; + configuration.originatorType = configuration.originator.entityType; + } else { + configuration.originatorId = null; + configuration.originatorType = null; + } + delete configuration.originator; + return configuration; + } + + testScript(debugEventBody?: DebugRuleNodeEventBody) { + const scriptLang: ScriptLanguage = this.generatorConfigForm.get('scriptLang').value; + const scriptField = scriptLang === ScriptLanguage.JS ? 'jsScript' : 'tbelScript'; + const helpId = scriptLang === ScriptLanguage.JS ? 'rulenode/generator_node_script_fn' : 'rulenode/tbel/generator_node_script_fn'; + const script: string = this.generatorConfigForm.get(scriptField).value; + this.nodeScriptTestService.testNodeScript( + script, + 'generate', + this.translate.instant('rule-node-config.generator'), + 'Generate', + ['prevMsg', 'prevMetadata', 'prevMsgType'], + this.ruleNodeId, + helpId, + scriptLang, + debugEventBody + ).subscribe((theScript) => { + if (theScript) { + this.generatorConfigForm.get(scriptField).setValue(theScript); + this.changeScript.emit(); + } + }); + } + + protected onValidate() { + const scriptLang: ScriptLanguage = this.generatorConfigForm.get('scriptLang').value; + if (scriptLang === ScriptLanguage.JS) { + this.jsFuncComponent.validateOnSubmit(); + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/gps-geo-action-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/gps-geo-action-config.component.html new file mode 100644 index 0000000000..73299f1031 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/gps-geo-action-config.component.html @@ -0,0 +1,195 @@ + +
    +
    +
    rule-node-config.coordinate-field-names
    +
    +
    + + {{ 'rule-node-config.latitude-field-name' | translate }} + + + {{ 'rule-node-config.latitude-field-name-required' | translate }} + + + + {{ 'rule-node-config.longitude-field-name' | translate }} + + + {{ 'rule-node-config.longitude-field-name-required' | translate }} + + +
    +
    rule-node-config.coordinate-field-hint
    +
    +
    +
    +
    rule-node-config.geofence-configuration
    +
    + + {{ 'rule-node-config.perimeter-type' | translate }} + + + {{ perimeterTypeTranslationMap.get(type) | translate }} + + + +
    + + {{ 'rule-node-config.fetch-perimeter-info-from-metadata' | translate }} + +
    + + {{ 'rule-node-config.perimeter-key-name' | translate }} + + + {{ 'rule-node-config.perimeter-key-name-required' | translate }} + + {{ 'rule-node-config.perimeter-key-name-hint' | translate }} + +
    +
    + + {{ 'rule-node-config.circle-center-latitude' | translate }} + + + {{ 'rule-node-config.circle-center-latitude-required' | translate }} + + + + {{ 'rule-node-config.circle-center-longitude' | translate }} + + + {{ 'rule-node-config.circle-center-longitude-required' | translate }} + + +
    +
    + + {{ 'rule-node-config.range' | translate }} + + + {{ 'rule-node-config.range-required' | translate }} + + + + {{ 'rule-node-config.range-units' | translate }} + + + {{ rangeUnitTranslationMap.get(type) | translate }} + + + + {{ 'rule-node-config.range-units-required' | translate }} + + +
    +
    +
    + + rule-node-config.polygon-definition + + + help + + + {{ 'rule-node-config.polygon-definition-required' | translate }} + + +
    +
    +
    +
    +
    +
    {{ 'rule-node-config.presence-monitoring-strategy' | translate }}
    + + + {{ presenceMonitoringStrategies.get(strategy).name | translate }} + + +
    +
    {{ geoActionConfigForm.get('reportPresenceStatusOnEachMessage').value === false ? + ('rule-node-config.presence-monitoring-strategy-on-first-message-hint' | translate) : + ('rule-node-config.presence-monitoring-strategy-on-each-message-hint' | translate) }} +
    +
    +
    +
    + + rule-node-config.min-inside-duration + + + {{ 'rule-node-config.min-inside-duration-value-required' | translate }} + + + {{ 'rule-node-config.time-value-range' | translate }} + + + {{ 'rule-node-config.time-value-range' | translate }} + + + + rule-node-config.min-inside-duration-time-unit + + + {{ timeUnitsTranslationMap.get(timeUnit) | translate }} + + + +
    +
    + + rule-node-config.min-outside-duration + + + {{ 'rule-node-config.min-outside-duration-value-required' | translate }} + + + {{ 'rule-node-config.time-value-range' | translate }} + + + {{ 'rule-node-config.time-value-range' | translate }} + + + + rule-node-config.min-outside-duration-time-unit + + + {{ timeUnitsTranslationMap.get(timeUnit) | translate }} + + + +
    +
    +
    +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/gps-geo-action-config.component.scss b/ui-ngx/src/app/modules/home/components/rule-node/action/gps-geo-action-config.component.scss new file mode 100644 index 0000000000..f177555e37 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/gps-geo-action-config.component.scss @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + .slide-toggle { + margin-bottom: 18px; + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/gps-geo-action-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/action/gps-geo-action-config.component.ts new file mode 100644 index 0000000000..47347edadf --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/gps-geo-action-config.component.ts @@ -0,0 +1,126 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; +import { + PerimeterType, + perimeterTypeTranslations, + PresenceMonitoringStrategiesData, + RangeUnit, + rangeUnitTranslations, + TimeUnit, + timeUnitTranslations +} from '../rule-node-config.models'; + +@Component({ + selector: 'tb-action-node-gps-geofencing-config', + templateUrl: './gps-geo-action-config.component.html', + styleUrls: ['./gps-geo-action-config.component.scss'] +}) +export class GpsGeoActionConfigComponent extends RuleNodeConfigurationComponent { + + geoActionConfigForm: UntypedFormGroup; + + perimeterType = PerimeterType; + perimeterTypes = Object.keys(PerimeterType); + perimeterTypeTranslationMap = perimeterTypeTranslations; + + rangeUnits = Object.keys(RangeUnit); + rangeUnitTranslationMap = rangeUnitTranslations; + + presenceMonitoringStrategies = PresenceMonitoringStrategiesData; + presenceMonitoringStrategyKeys = Array.from(this.presenceMonitoringStrategies.keys()); + + timeUnits = Object.keys(TimeUnit); + timeUnitsTranslationMap = timeUnitTranslations; + + public defaultPaddingEnable = true; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.geoActionConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.geoActionConfigForm = this.fb.group({ + reportPresenceStatusOnEachMessage: [configuration ? configuration.reportPresenceStatusOnEachMessage : true, + [Validators.required]], + latitudeKeyName: [configuration ? configuration.latitudeKeyName : null, [Validators.required]], + longitudeKeyName: [configuration ? configuration.longitudeKeyName : null, [Validators.required]], + perimeterType: [configuration ? configuration.perimeterType : null, [Validators.required]], + fetchPerimeterInfoFromMessageMetadata: [configuration ? configuration.fetchPerimeterInfoFromMessageMetadata : false, []], + perimeterKeyName: [configuration ? configuration.perimeterKeyName : null, []], + centerLatitude: [configuration ? configuration.centerLatitude : null, []], + centerLongitude: [configuration ? configuration.centerLatitude : null, []], + range: [configuration ? configuration.range : null, []], + rangeUnit: [configuration ? configuration.rangeUnit : null, []], + polygonsDefinition: [configuration ? configuration.polygonsDefinition : null, []], + minInsideDuration: [configuration ? configuration.minInsideDuration : null, + [Validators.required, Validators.min(1), Validators.max(2147483647)]], + minInsideDurationTimeUnit: [configuration ? configuration.minInsideDurationTimeUnit : null, [Validators.required]], + minOutsideDuration: [configuration ? configuration.minOutsideDuration : null, + [Validators.required, Validators.min(1), Validators.max(2147483647)]], + minOutsideDurationTimeUnit: [configuration ? configuration.minOutsideDurationTimeUnit : null, [Validators.required]], + }); + } + + protected validatorTriggers(): string[] { + return ['fetchPerimeterInfoFromMessageMetadata', 'perimeterType']; + } + + protected updateValidators(emitEvent: boolean) { + const fetchPerimeterInfoFromMessageMetadata: boolean = this.geoActionConfigForm.get('fetchPerimeterInfoFromMessageMetadata').value; + const perimeterType: PerimeterType = this.geoActionConfigForm.get('perimeterType').value; + if (fetchPerimeterInfoFromMessageMetadata) { + this.geoActionConfigForm.get('perimeterKeyName').setValidators([Validators.required]); + } else { + this.geoActionConfigForm.get('perimeterKeyName').setValidators([]); + } + if (!fetchPerimeterInfoFromMessageMetadata && perimeterType === PerimeterType.CIRCLE) { + this.geoActionConfigForm.get('centerLatitude').setValidators([Validators.required, + Validators.min(-90), Validators.max(90)]); + this.geoActionConfigForm.get('centerLongitude').setValidators([Validators.required, + Validators.min(-180), Validators.max(180)]); + this.geoActionConfigForm.get('range').setValidators([Validators.required, Validators.min(0)]); + this.geoActionConfigForm.get('rangeUnit').setValidators([Validators.required]); + + this.defaultPaddingEnable = false; + } else { + this.geoActionConfigForm.get('centerLatitude').setValidators([]); + this.geoActionConfigForm.get('centerLongitude').setValidators([]); + this.geoActionConfigForm.get('range').setValidators([]); + this.geoActionConfigForm.get('rangeUnit').setValidators([]); + + this.defaultPaddingEnable = true; + } + if (!fetchPerimeterInfoFromMessageMetadata && perimeterType === PerimeterType.POLYGON) { + this.geoActionConfigForm.get('polygonsDefinition').setValidators([Validators.required]); + } else { + this.geoActionConfigForm.get('polygonsDefinition').setValidators([]); + } + this.geoActionConfigForm.get('perimeterKeyName').updateValueAndValidity({emitEvent}); + this.geoActionConfigForm.get('centerLatitude').updateValueAndValidity({emitEvent}); + this.geoActionConfigForm.get('centerLongitude').updateValueAndValidity({emitEvent}); + this.geoActionConfigForm.get('range').updateValueAndValidity({emitEvent}); + this.geoActionConfigForm.get('rangeUnit').updateValueAndValidity({emitEvent}); + this.geoActionConfigForm.get('polygonsDefinition').updateValueAndValidity({emitEvent}); + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/log-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/log-config.component.html new file mode 100644 index 0000000000..c47644ae5f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/log-config.component.html @@ -0,0 +1,59 @@ + +
    + + + + + + + +
    + +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/log-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/action/log-config.component.ts new file mode 100644 index 0000000000..4a8017b6ff --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/log-config.component.ts @@ -0,0 +1,124 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, EventEmitter, ViewChild } from '@angular/core'; +import { getCurrentAuthState, NodeScriptTestService } from '@core/public-api'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; +import { + RuleNodeConfiguration, + RuleNodeConfigurationComponent, + ScriptLanguage +} from '@app/shared/models/rule-node.models'; +import type { JsFuncComponent } from '@app/shared/components/js-func.component'; +import { DebugRuleNodeEventBody } from '@shared/models/event.models'; + +@Component({ + selector: 'tb-action-node-log-config', + templateUrl: './log-config.component.html', + styleUrls: [] +}) +export class LogConfigComponent extends RuleNodeConfigurationComponent { + + @ViewChild('jsFuncComponent', {static: false}) jsFuncComponent: JsFuncComponent; + @ViewChild('tbelFuncComponent', {static: false}) tbelFuncComponent: JsFuncComponent; + + logConfigForm: UntypedFormGroup; + + tbelEnabled = getCurrentAuthState(this.store).tbelEnabled; + + scriptLanguage = ScriptLanguage; + + changeScript: EventEmitter = new EventEmitter(); + + readonly hasScript = true; + + readonly testScriptLabel = 'rule-node-config.test-to-string-function'; + + constructor(private fb: UntypedFormBuilder, + private nodeScriptTestService: NodeScriptTestService, + private translate: TranslateService) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.logConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.logConfigForm = this.fb.group({ + scriptLang: [configuration ? configuration.scriptLang : ScriptLanguage.JS, [Validators.required]], + jsScript: [configuration ? configuration.jsScript : null, []], + tbelScript: [configuration ? configuration.tbelScript : null, []] + }); + } + + protected validatorTriggers(): string[] { + return ['scriptLang']; + } + + protected updateValidators(emitEvent: boolean) { + let scriptLang: ScriptLanguage = this.logConfigForm.get('scriptLang').value; + if (scriptLang === ScriptLanguage.TBEL && !this.tbelEnabled) { + scriptLang = ScriptLanguage.JS; + this.logConfigForm.get('scriptLang').patchValue(scriptLang, {emitEvent: false}); + setTimeout(() => {this.logConfigForm.updateValueAndValidity({emitEvent: true});}); + } + this.logConfigForm.get('jsScript').setValidators(scriptLang === ScriptLanguage.JS ? [Validators.required] : []); + this.logConfigForm.get('jsScript').updateValueAndValidity({emitEvent}); + this.logConfigForm.get('tbelScript').setValidators(scriptLang === ScriptLanguage.TBEL ? [Validators.required] : []); + this.logConfigForm.get('tbelScript').updateValueAndValidity({emitEvent}); + } + + protected prepareInputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + if (configuration) { + if (!configuration.scriptLang) { + configuration.scriptLang = ScriptLanguage.JS; + } + } + return configuration; + } + + testScript(debugEventBody?: DebugRuleNodeEventBody) { + const scriptLang: ScriptLanguage = this.logConfigForm.get('scriptLang').value; + const scriptField = scriptLang === ScriptLanguage.JS ? 'jsScript' : 'tbelScript'; + const helpId = scriptLang === ScriptLanguage.JS ? 'rulenode/log_node_script_fn' : 'rulenode/tbel/log_node_script_fn'; + const script: string = this.logConfigForm.get(scriptField).value; + this.nodeScriptTestService.testNodeScript( + script, + 'string', + this.translate.instant('rule-node-config.to-string'), + 'ToString', + ['msg', 'metadata', 'msgType'], + this.ruleNodeId, + helpId, + scriptLang, + debugEventBody + ).subscribe((theScript) => { + if (theScript) { + this.logConfigForm.get(scriptField).setValue(theScript); + this.changeScript.emit(); + } + }); + } + + protected onValidate() { + const scriptLang: ScriptLanguage = this.logConfigForm.get('scriptLang').value; + if (scriptLang === ScriptLanguage.JS) { + this.jsFuncComponent.validateOnSubmit(); + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/math-function-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/math-function-config.component.html new file mode 100644 index 0000000000..c8d7ba8b2e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/math-function-config.component.html @@ -0,0 +1,103 @@ + +
    + + +
    + rule-node-config.argument-tile + + +
    +
    + {{'rule-node-config.custom-expression-field-input' | translate }} * + + + + rule-node-config.custom-expression-field-input-required + + rule-node-config.custom-expression-field-input-hint + +
    +
    + rule-node-config.result-title +
    + + rule-node-config.type-field-input + + + {{ argumentTypeResultMap.get(mathFunctionConfigForm.get('result.type').value)?.name | translate }} + + + {{ argumentTypeResultMap.get(argument).name | translate }} + + {{ argumentTypeResultMap.get(argument).description | translate }} + + + + + rule-node-config.type-field-input-required + + +
    + + rule-node-config.attribute-scope-field-input + + + {{ attributeScopeMap.get(scope) | translate }} + + + + + rule-node-config.key-field-input + + help + + rule-node-config.key-field-input-required + + +
    +
    + + rule-node-config.number-floating-point-field-input + + + +
    +
    + + {{'rule-node-config.add-to-message-field-input' | translate }} + + + {{'rule-node-config.add-to-metadata-field-input' | translate}} + +
    +
    +
    +
    diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/customer/CustomerMsgConstructorFactory.java b/ui-ngx/src/app/modules/home/components/rule-node/action/math-function-config.component.scss similarity index 59% rename from application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/customer/CustomerMsgConstructorFactory.java rename to ui-ngx/src/app/modules/home/components/rule-node/action/math-function-config.component.scss index 01df167527..d96fbe7578 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/customer/CustomerMsgConstructorFactory.java +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/math-function-config.component.scss @@ -13,14 +13,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.edge.rpc.constructor.customer; +:host ::ng-deep { + .fields-group { + padding: 0 16px 8px; + margin: 10px 0; + border: 1px groove rgba(0, 0, 0, .25); + border-radius: 4px; -import org.springframework.stereotype.Component; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.constructor.BaseMsgConstructorFactory; + .mat-mdc-form-field { + .mat-mdc-form-field-infix { + width: 100%; + } + } -@Component -@TbCoreComponent -public class CustomerMsgConstructorFactory extends BaseMsgConstructorFactory { + legend { + color: rgba(0, 0, 0, .7); + width: fit-content; + } + legend + * { + display: block; + &.no-margin-top { + margin-top: 0; + } + } + } } diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/math-function-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/action/math-function-config.component.ts new file mode 100644 index 0000000000..2ff796840e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/math-function-config.component.ts @@ -0,0 +1,92 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@app/shared/models/rule-node.models'; +import { + ArgumentTypeResult, + ArgumentTypeResultMap, + AttributeScopeMap, + AttributeScopeResult, + MathFunction +} from '../rule-node-config.models'; + + +@Component({ + selector: 'tb-action-node-math-function-config', + templateUrl: './math-function-config.component.html', + styleUrls: ['./math-function-config.component.scss'] +}) +export class MathFunctionConfigComponent extends RuleNodeConfigurationComponent { + + mathFunctionConfigForm: UntypedFormGroup; + + MathFunction = MathFunction; + ArgumentTypeResult = ArgumentTypeResult; + argumentTypeResultMap = ArgumentTypeResultMap; + attributeScopeMap = AttributeScopeMap; + argumentsResult = Object.values(ArgumentTypeResult); + attributeScopeResult = Object.values(AttributeScopeResult); + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.mathFunctionConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.mathFunctionConfigForm = this.fb.group({ + operation: [configuration ? configuration.operation : null, [Validators.required]], + arguments: [configuration ? configuration.arguments : null, [Validators.required]], + customFunction: [configuration ? configuration.customFunction : '', [Validators.required]], + result: this.fb.group({ + type: [configuration ? configuration.result.type: null, [Validators.required]], + attributeScope: [configuration ? configuration.result.attributeScope : null, [Validators.required]], + key: [configuration ? configuration.result.key : '', [Validators.required]], + resultValuePrecision: [configuration ? configuration.result.resultValuePrecision : 0], + addToBody: [configuration ? configuration.result.addToBody : false], + addToMetadata: [configuration ? configuration.result.addToMetadata : false] + }) + }); + } + + protected updateValidators(emitEvent: boolean) { + const operation: MathFunction = this.mathFunctionConfigForm.get('operation').value; + const resultType: ArgumentTypeResult = this.mathFunctionConfigForm.get('result.type').value; + if (operation === MathFunction.CUSTOM) { + this.mathFunctionConfigForm.get('customFunction').enable({emitEvent: false}); + if (this.mathFunctionConfigForm.get('customFunction').value === null) { + this.mathFunctionConfigForm.get('customFunction').patchValue('(x - 32) / 1.8', {emitEvent: false}); + } + } else { + this.mathFunctionConfigForm.get('customFunction').disable({emitEvent: false}); + } + if (resultType === ArgumentTypeResult.ATTRIBUTE) { + this.mathFunctionConfigForm.get('result.attributeScope').enable({emitEvent: false}); + } else { + this.mathFunctionConfigForm.get('result.attributeScope').disable({emitEvent: false}); + } + this.mathFunctionConfigForm.get('customFunction').updateValueAndValidity({emitEvent}); + this.mathFunctionConfigForm.get('result.attributeScope').updateValueAndValidity({emitEvent}); + } + + protected validatorTriggers(): string[] { + return ['operation', 'result.type']; + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/msg-count-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/msg-count-config.component.html new file mode 100644 index 0000000000..586addf563 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/msg-count-config.component.html @@ -0,0 +1,36 @@ + +
    + + rule-node-config.interval-seconds + + + {{ 'rule-node-config.interval-seconds-required' | translate }} + + + {{ 'rule-node-config.min-interval-seconds-message' | translate }} + + + + rule-node-config.output-timeseries-key-prefix + + + {{ 'rule-node-config.output-timeseries-key-prefix-required' | translate }} + + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/msg-count-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/action/msg-count-config.component.ts new file mode 100644 index 0000000000..5380c569a0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/msg-count-config.component.ts @@ -0,0 +1,45 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; + +@Component({ + selector: 'tb-action-node-msg-count-config', + templateUrl: './msg-count-config.component.html', + styleUrls: [] +}) +export class MsgCountConfigComponent extends RuleNodeConfigurationComponent { + + msgCountConfigForm: UntypedFormGroup; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.msgCountConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.msgCountConfigForm = this.fb.group({ + interval: [configuration ? configuration.interval : null, [Validators.required, Validators.min(1)]], + telemetryPrefix: [configuration ? configuration.telemetryPrefix : null, [Validators.required]] + }); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/msg-delay-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/msg-delay-config.component.html new file mode 100644 index 0000000000..9f549a0d6d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/msg-delay-config.component.html @@ -0,0 +1,57 @@ + +
    + + {{ 'rule-node-config.use-metadata-period-in-seconds-patterns' | translate }} + +
    rule-node-config.use-metadata-period-in-seconds-patterns-hint
    + + rule-node-config.period-seconds + + + {{ 'rule-node-config.period-seconds-required' | translate }} + + + {{ 'rule-node-config.min-period-0-seconds-message' | translate }} + + + + + rule-node-config.period-in-seconds-pattern + + + {{ 'rule-node-config.period-in-seconds-pattern-required' | translate }} + + rule-node-config.general-pattern-hint + + + + rule-node-config.max-pending-messages + + + {{ 'rule-node-config.max-pending-messages-required' | translate }} + + + {{ 'rule-node-config.max-pending-messages-range' | translate }} + + + {{ 'rule-node-config.max-pending-messages-range' | translate }} + + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/msg-delay-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/action/msg-delay-config.component.ts new file mode 100644 index 0000000000..cabf2e67af --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/msg-delay-config.component.ts @@ -0,0 +1,65 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; + +@Component({ + selector: 'tb-action-node-msg-delay-config', + templateUrl: './msg-delay-config.component.html', + styleUrls: [] +}) +export class MsgDelayConfigComponent extends RuleNodeConfigurationComponent { + + msgDelayConfigForm: UntypedFormGroup; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.msgDelayConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.msgDelayConfigForm = this.fb.group({ + useMetadataPeriodInSecondsPatterns: [configuration ? configuration.useMetadataPeriodInSecondsPatterns : false, []], + periodInSeconds: [configuration ? configuration.periodInSeconds : null, []], + periodInSecondsPattern: [configuration ? configuration.periodInSecondsPattern : null, []], + maxPendingMsgs: [configuration ? configuration.maxPendingMsgs : null, + [Validators.required, Validators.min(1), Validators.max(100000)]], + }); + } + + protected validatorTriggers(): string[] { + return ['useMetadataPeriodInSecondsPatterns']; + } + + protected updateValidators(emitEvent: boolean) { + const useMetadataPeriodInSecondsPatterns: boolean = this.msgDelayConfigForm.get('useMetadataPeriodInSecondsPatterns').value; + if (useMetadataPeriodInSecondsPatterns) { + this.msgDelayConfigForm.get('periodInSecondsPattern').setValidators([Validators.required]); + this.msgDelayConfigForm.get('periodInSeconds').setValidators([]); + } else { + this.msgDelayConfigForm.get('periodInSecondsPattern').setValidators([]); + this.msgDelayConfigForm.get('periodInSeconds').setValidators([Validators.required, Validators.min(0)]); + } + this.msgDelayConfigForm.get('periodInSecondsPattern').updateValueAndValidity({emitEvent}); + this.msgDelayConfigForm.get('periodInSeconds').updateValueAndValidity({emitEvent}); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/push-to-cloud-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/push-to-cloud-config.component.html new file mode 100644 index 0000000000..a8c1975e6b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/push-to-cloud-config.component.html @@ -0,0 +1,49 @@ + +
    +
    + + +
    + + {{ 'rule-node-config.attributes-scope' | translate }} + + + {{ telemetryTypeTranslationsMap.get(scope) | translate }} + + + + + {{ 'rule-node-config.attributes-scope-value' | translate }} + + + +
    +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/push-to-cloud-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/action/push-to-cloud-config.component.ts new file mode 100644 index 0000000000..07082ebabe --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/push-to-cloud-config.component.ts @@ -0,0 +1,48 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; +import { AttributeScope, telemetryTypeTranslations } from '@shared/models/telemetry/telemetry.models'; + +@Component({ + selector: 'tb-action-node-push-to-cloud-config', + templateUrl: './push-to-cloud-config.component.html', + styleUrls: [] +}) +export class PushToCloudConfigComponent extends RuleNodeConfigurationComponent { + + attributeScopes = Object.keys(AttributeScope); + telemetryTypeTranslationsMap = telemetryTypeTranslations; + + pushToCloudConfigForm: UntypedFormGroup; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.pushToCloudConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.pushToCloudConfigForm = this.fb.group({ + scope: [configuration ? configuration.scope : null, [Validators.required]] + }); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/push-to-edge-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/push-to-edge-config.component.html new file mode 100644 index 0000000000..4e79a763c0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/push-to-edge-config.component.html @@ -0,0 +1,49 @@ + +
    +
    + + +
    + + {{ 'rule-node-config.attributes-scope' | translate }} + + + {{ telemetryTypeTranslationsMap.get(scope) | translate }} + + + + + {{ 'rule-node-config.attributes-scope-value' | translate }} + + + +
    +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/push-to-edge-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/action/push-to-edge-config.component.ts new file mode 100644 index 0000000000..bbefcd3d76 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/push-to-edge-config.component.ts @@ -0,0 +1,48 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; +import { AttributeScope, telemetryTypeTranslations } from '@shared/models/telemetry/telemetry.models'; + +@Component({ + selector: 'tb-action-node-push-to-edge-config', + templateUrl: './push-to-edge-config.component.html', + styleUrls: [] +}) +export class PushToEdgeConfigComponent extends RuleNodeConfigurationComponent { + + attributeScopes = Object.keys(AttributeScope); + telemetryTypeTranslationsMap = telemetryTypeTranslations; + + pushToEdgeConfigForm: UntypedFormGroup; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.pushToEdgeConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.pushToEdgeConfigForm = this.fb.group({ + scope: [configuration ? configuration.scope : null, [Validators.required]] + }); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/rpc-reply-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/rpc-reply-config.component.html new file mode 100644 index 0000000000..be1ac0a0eb --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/rpc-reply-config.component.html @@ -0,0 +1,36 @@ + +
    +
    rule-node-config.reply-routing-configuration
    + + +
    + + rule-node-config.service-id-metadata-attribute + + + + rule-node-config.session-id-metadata-attribute + + + + rule-node-config.request-id-metadata-attribute + + +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/rpc-reply-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/action/rpc-reply-config.component.ts new file mode 100644 index 0000000000..840c6eb9c3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/rpc-reply-config.component.ts @@ -0,0 +1,45 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; + +@Component({ + selector: 'tb-action-node-rpc-reply-config', + templateUrl: './rpc-reply-config.component.html', + styleUrls: [] +}) +export class RpcReplyConfigComponent extends RuleNodeConfigurationComponent { + + rpcReplyConfigForm: UntypedFormGroup; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.rpcReplyConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.rpcReplyConfigForm = this.fb.group({ + serviceIdMetaDataAttribute: [configuration ? configuration.serviceIdMetaDataAttribute : null, []], + sessionIdMetaDataAttribute: [configuration ? configuration.sessionIdMetaDataAttribute : null, []], + requestIdMetaDataAttribute: [configuration ? configuration.requestIdMetaDataAttribute : null, []] + }); + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/rpc-request-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/rpc-request-config.component.html new file mode 100644 index 0000000000..306565590e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/rpc-request-config.component.html @@ -0,0 +1,29 @@ + +
    + + rule-node-config.timeout-sec + + + {{ 'rule-node-config.timeout-required' | translate }} + + + {{ 'rule-node-config.min-timeout-message' | translate }} + + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/rpc-request-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/action/rpc-request-config.component.ts new file mode 100644 index 0000000000..d54677b237 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/rpc-request-config.component.ts @@ -0,0 +1,43 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; + +@Component({ + selector: 'tb-action-node-rpc-request-config', + templateUrl: './rpc-request-config.component.html', + styleUrls: [] +}) +export class RpcRequestConfigComponent extends RuleNodeConfigurationComponent { + + rpcRequestConfigForm: UntypedFormGroup; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.rpcRequestConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.rpcRequestConfigForm = this.fb.group({ + timeoutInSeconds: [configuration ? configuration.timeoutInSeconds : null, [Validators.required, Validators.min(0)]] + }); + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/save-to-custom-table-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/save-to-custom-table-config.component.html new file mode 100644 index 0000000000..5f2d7f823d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/save-to-custom-table-config.component.html @@ -0,0 +1,56 @@ + +
    + + rule-node-config.custom-table-name + + + help + + + {{ 'rule-node-config.custom-table-name-required' | translate }} + + + + + + rule-node-config.default-ttl + + rule-node-config.default-ttl-zero-hint + + {{ 'rule-node-config.min-default-ttl-message' | translate }} + + + {{ 'rule-node-config.default-ttl-required' | translate }} + + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/save-to-custom-table-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/action/save-to-custom-table-config.component.ts new file mode 100644 index 0000000000..27d40dd4b9 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/save-to-custom-table-config.component.ts @@ -0,0 +1,50 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; + +@Component({ + selector: 'tb-action-node-custom-table-config', + templateUrl: './save-to-custom-table-config.component.html', + styleUrls: [] +}) +export class SaveToCustomTableConfigComponent extends RuleNodeConfigurationComponent { + + saveToCustomTableConfigForm: UntypedFormGroup; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.saveToCustomTableConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.saveToCustomTableConfigForm = this.fb.group({ + tableName: [configuration ? configuration.tableName : null, [Validators.required, Validators.pattern(/.*\S.*/)]], + fieldsMapping: [configuration ? configuration.fieldsMapping : null, [Validators.required]], + defaultTtl: [configuration ? configuration.defaultTtl : 0, [Validators.required, Validators.min(0)]] + }); + } + + protected prepareOutputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + configuration.tableName = configuration.tableName.trim(); + return configuration; + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/send-rest-api-call-reply-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/send-rest-api-call-reply-config.component.html new file mode 100644 index 0000000000..3a8be8d22f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/send-rest-api-call-reply-config.component.html @@ -0,0 +1,32 @@ + +
    +
    rule-node-config.reply-routing-configuration
    + + +
    + + rule-node-config.service-id-metadata-attribute + + + + rule-node-config.request-id-metadata-attribute + + +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/send-rest-api-call-reply-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/action/send-rest-api-call-reply-config.component.ts new file mode 100644 index 0000000000..ad4e148658 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/send-rest-api-call-reply-config.component.ts @@ -0,0 +1,44 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; + +@Component({ + selector: 'tb-action-node-send-rest-api-call-reply-config', + templateUrl: './send-rest-api-call-reply-config.component.html', + styleUrls: [] +}) +export class SendRestApiCallReplyConfigComponent extends RuleNodeConfigurationComponent { + + sendRestApiCallReplyConfigForm: UntypedFormGroup; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.sendRestApiCallReplyConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.sendRestApiCallReplyConfigForm = this.fb.group({ + requestIdMetaDataAttribute: [configuration ? configuration.requestIdMetaDataAttribute : null, []], + serviceIdMetaDataAttribute: [configuration ? configuration.serviceIdMetaDataAttribute : null, []] + }); + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/timeseries-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/timeseries-config.component.html new file mode 100644 index 0000000000..715776d780 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/timeseries-config.component.html @@ -0,0 +1,89 @@ + +
    +
    +
    +
    + rule-node-config.save-time-series.processing-settings +
    + + {{ 'rule-node-config.basic-mode' | translate}} + {{ 'rule-node-config.advanced-mode' | translate }} + +
    + @if(!timeseriesConfigForm.get('processingSettings.isAdvanced').value) { + + rule-node-config.save-time-series.strategy + + @for (strategy of persistenceStrategies; track strategy) { + {{ PersistenceTypeTranslationMap.get(strategy) | translate }} + } + + + + @if(timeseriesConfigForm.get('processingSettings.type').value === PersistenceType.DEDUPLICATE) { + + + } + } @else { + + } +
    +
    + + + rule-node-config.advanced-settings + + +
    + + {{ 'rule-node-config.use-server-ts' | translate }} + +
    + + + help + + +
    +
    +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/timeseries-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/action/timeseries-config.component.ts new file mode 100644 index 0000000000..d5e1d998a8 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/timeseries-config.component.ts @@ -0,0 +1,132 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; +import { + defaultAdvancedPersistenceStrategy, + maxDeduplicateTimeSecs, + ProcessingSettings, + ProcessingSettingsForm, + ProcessingType, + ProcessingTypeTranslationMap, + TimeseriesNodeConfiguration, + TimeseriesNodeConfigurationForm +} from '@home/components/rule-node/action/timeseries-config.models'; + +@Component({ + selector: 'tb-action-node-timeseries-config', + templateUrl: './timeseries-config.component.html', + styleUrls: [] +}) +export class TimeseriesConfigComponent extends RuleNodeConfigurationComponent { + + timeseriesConfigForm: FormGroup; + + PersistenceType = ProcessingType; + persistenceStrategies = [ProcessingType.ON_EVERY_MESSAGE, ProcessingType.DEDUPLICATE, ProcessingType.WEBSOCKETS_ONLY]; + PersistenceTypeTranslationMap = ProcessingTypeTranslationMap; + + maxDeduplicateTime = maxDeduplicateTimeSecs + + constructor(private fb: FormBuilder) { + super(); + } + + protected configForm(): FormGroup { + return this.timeseriesConfigForm; + } + + protected validatorTriggers(): string[] { + return ['processingSettings.isAdvanced', 'processingSettings.type']; + } + + protected prepareInputConfig(config: TimeseriesNodeConfiguration): TimeseriesNodeConfigurationForm { + let processingSettings: ProcessingSettingsForm; + if (config?.processingSettings) { + const isAdvanced = config?.processingSettings?.type === ProcessingType.ADVANCED; + processingSettings = { + type: isAdvanced ? ProcessingType.ON_EVERY_MESSAGE : config.processingSettings.type, + isAdvanced: isAdvanced, + deduplicationIntervalSecs: config.processingSettings?.deduplicationIntervalSecs ?? 60, + advanced: isAdvanced ? config.processingSettings : defaultAdvancedPersistenceStrategy + } + } else { + processingSettings = { + type: ProcessingType.ON_EVERY_MESSAGE, + isAdvanced: false, + deduplicationIntervalSecs: 60, + advanced: defaultAdvancedPersistenceStrategy + }; + } + return { + ...config, + processingSettings: processingSettings + } + } + + protected prepareOutputConfig(config: TimeseriesNodeConfigurationForm): TimeseriesNodeConfiguration { + let processingSettings: ProcessingSettings; + if (config.processingSettings.isAdvanced) { + processingSettings = { + ...config.processingSettings.advanced, + type: ProcessingType.ADVANCED + }; + } else { + processingSettings = { + type: config.processingSettings.type, + deduplicationIntervalSecs: config.processingSettings?.deduplicationIntervalSecs + }; + } + return { + ...config, + processingSettings + }; + } + + protected onConfigurationSet(config: TimeseriesNodeConfigurationForm) { + this.timeseriesConfigForm = this.fb.group({ + processingSettings: this.fb.group({ + isAdvanced: [config?.processingSettings?.isAdvanced ?? false], + type: [config?.processingSettings?.type ?? ProcessingType.ON_EVERY_MESSAGE], + deduplicationIntervalSecs: [ + {value: config?.processingSettings?.deduplicationIntervalSecs ?? 60, disabled: true}, + [Validators.required, Validators.max(maxDeduplicateTimeSecs)] + ], + advanced: [{value: null, disabled: true}] + }), + defaultTTL: [config?.defaultTTL ?? null, [Validators.required, Validators.min(0)]], + useServerTs: [config?.useServerTs ?? false] + }); + } + + protected updateValidators(emitEvent: boolean, _trigger?: string) { + const processingForm = this.timeseriesConfigForm.get('processingSettings') as FormGroup; + const isAdvanced: boolean = processingForm.get('isAdvanced').value; + const type: ProcessingType = processingForm.get('type').value; + if (!isAdvanced && type === ProcessingType.DEDUPLICATE) { + processingForm.get('deduplicationIntervalSecs').enable({emitEvent}); + } else { + processingForm.get('deduplicationIntervalSecs').disable({emitEvent}); + } + if (isAdvanced) { + processingForm.get('advanced').enable({emitEvent}); + } else { + processingForm.get('advanced').disable({emitEvent}); + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/timeseries-config.models.ts b/ui-ngx/src/app/modules/home/components/rule-node/action/timeseries-config.models.ts new file mode 100644 index 0000000000..e9d024663d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/timeseries-config.models.ts @@ -0,0 +1,78 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { DAY, SECOND } from '@shared/models/time/time.models'; + +export const maxDeduplicateTimeSecs = DAY / SECOND; + +export interface TimeseriesNodeConfiguration { + processingSettings: ProcessingSettings; + defaultTTL: number; + useServerTs: boolean; +} + +export interface TimeseriesNodeConfigurationForm extends Omit { + processingSettings: ProcessingSettingsForm +} + +export type ProcessingSettings = BasicProcessingSettings & Partial & Partial; + +export type ProcessingSettingsForm = Omit & { + isAdvanced: boolean; + advanced?: Partial; + type: ProcessingType; +}; + +export enum ProcessingType { + ON_EVERY_MESSAGE = 'ON_EVERY_MESSAGE', + DEDUPLICATE = 'DEDUPLICATE', + WEBSOCKETS_ONLY = 'WEBSOCKETS_ONLY', + ADVANCED = 'ADVANCED', + SKIP = 'SKIP' +} + +export const ProcessingTypeTranslationMap = new Map([ + [ProcessingType.ON_EVERY_MESSAGE, 'rule-node-config.save-time-series.strategy-type.every-message'], + [ProcessingType.DEDUPLICATE, 'rule-node-config.save-time-series.strategy-type.deduplicate'], + [ProcessingType.WEBSOCKETS_ONLY, 'rule-node-config.save-time-series.strategy-type.web-sockets-only'], + [ProcessingType.SKIP, 'rule-node-config.save-time-series.strategy-type.skip'], +]) + +export interface BasicProcessingSettings { + type: ProcessingType; +} + +export interface DeduplicateProcessingStrategy extends BasicProcessingSettings{ + deduplicationIntervalSecs: number; +} + +export interface AdvancedProcessingStrategy extends BasicProcessingSettings{ + timeseries: AdvancedProcessingConfig; + latest: AdvancedProcessingConfig; + webSockets: AdvancedProcessingConfig; +} + +export type AdvancedProcessingConfig = WithOptional; + +export const defaultAdvancedProcessingConfig: AdvancedProcessingConfig = { + type: ProcessingType.ON_EVERY_MESSAGE +} + +export const defaultAdvancedPersistenceStrategy: Omit = { + timeseries: defaultAdvancedProcessingConfig, + latest: defaultAdvancedProcessingConfig, + webSockets: defaultAdvancedProcessingConfig, +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/unassign-customer-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/unassign-customer-config.component.html new file mode 100644 index 0000000000..4b2df342ab --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/unassign-customer-config.component.html @@ -0,0 +1,39 @@ + +
    +
    + +
    +
    + + {{ 'rule-node-config.unassign-from-customer' | translate }} + +
    + + rule-node-config.customer-name-pattern + + + {{ 'rule-node-config.customer-name-pattern-required' | translate }} + + rule-node-config.customer-name-pattern-hint + +
    +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/unassign-customer-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/action/unassign-customer-config.component.ts new file mode 100644 index 0000000000..a514a5b495 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/unassign-customer-config.component.ts @@ -0,0 +1,72 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { isDefinedAndNotNull } from '@core/public-api'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@app/shared/models/rule-node.models'; + +@Component({ + selector: 'tb-action-node-un-assign-to-customer-config', + templateUrl: './unassign-customer-config.component.html', + styleUrls: [] +}) +export class UnassignCustomerConfigComponent extends RuleNodeConfigurationComponent { + + unassignCustomerConfigForm: UntypedFormGroup; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.unassignCustomerConfigForm; + } + + protected prepareInputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + return { + customerNamePattern: isDefinedAndNotNull(configuration?.customerNamePattern) ? configuration.customerNamePattern : null, + unassignFromCustomer: isDefinedAndNotNull(configuration?.customerNamePattern), + }; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.unassignCustomerConfigForm = this.fb.group({ + customerNamePattern: [configuration.customerNamePattern , []], + unassignFromCustomer: [configuration.unassignFromCustomer, []] + }); + } + + protected validatorTriggers(): string[] { + return ['unassignFromCustomer']; + } + + protected updateValidators(emitEvent: boolean) { + const unassignFromCustomer: boolean = this.unassignCustomerConfigForm.get('unassignFromCustomer').value; + if (unassignFromCustomer) { + this.unassignCustomerConfigForm.get('customerNamePattern').setValidators([Validators.required, Validators.pattern(/.*\S.*/)]); + } else { + this.unassignCustomerConfigForm.get('customerNamePattern').setValidators([]); + } + this.unassignCustomerConfigForm.get('customerNamePattern').updateValueAndValidity({emitEvent}); + } + + protected prepareOutputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + return { + customerNamePattern: configuration.unassignFromCustomer ? configuration.customerNamePattern.trim() : null + }; + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/alarm-status-select.component.html b/ui-ngx/src/app/modules/home/components/rule-node/common/alarm-status-select.component.html new file mode 100644 index 0000000000..3406218f10 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/alarm-status-select.component.html @@ -0,0 +1,37 @@ + +
    + +
    + + {{ alarmStatusTranslations.get(alarmStatus.ACTIVE_UNACK) | translate }} + + + {{ alarmStatusTranslations.get(alarmStatus.ACTIVE_ACK) | translate }} + +
    +
    + + {{ alarmStatusTranslations.get(alarmStatus.CLEARED_UNACK) | translate }} + + + {{ alarmStatusTranslations.get(alarmStatus.CLEARED_ACK) | translate }} + +
    +
    +
    diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/customer/CustomerMsgConstructor.java b/ui-ngx/src/app/modules/home/components/rule-node/common/alarm-status-select.component.scss similarity index 50% rename from application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/customer/CustomerMsgConstructor.java rename to ui-ngx/src/app/modules/home/components/rule-node/common/alarm-status-select.component.scss index 289056fadf..4eca381bff 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/customer/CustomerMsgConstructor.java +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/alarm-status-select.component.scss @@ -13,17 +13,48 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.edge.rpc.constructor.customer; +:host { + .chip-listbox { + max-width: 460px; + width: 100%; -import org.thingsboard.server.common.data.Customer; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.gen.edge.v1.CustomerUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.service.edge.rpc.constructor.MsgConstructor; + .toggle-column { + display: flex; + flex: 1 1 100%; + gap: 8px; + } -public interface CustomerMsgConstructor extends MsgConstructor { + .option { + margin: 0; + } + } - CustomerUpdateMsg constructCustomerUpdatedMsg(UpdateMsgType msgType, Customer customer); + @media screen and (max-width: 959px) { + .chip-listbox { + max-width: 360px; - CustomerUpdateMsg constructCustomerDeleteMsg(CustomerId customerId); + .toggle-column { + flex-direction: column; + } + } + } +} + +:host ::ng-deep { + .chip-listbox { + .mdc-evolution-chip-set__chips { + gap: 8px; + } + + .option { + button { + flex-basis: 100%; + justify-content: start; + } + + .mdc-evolution-chip__graphic { + flex-grow: 0; + } + } + } } diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/alarm-status-select.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/common/alarm-status-select.component.ts new file mode 100644 index 0000000000..f92f09b428 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/alarm-status-select.component.ts @@ -0,0 +1,77 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, DestroyRef, forwardRef, OnInit } from '@angular/core'; +import { AlarmStatus, alarmStatusTranslations, PageComponent } from '@shared/public-api'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; + +@Component({ + selector: 'tb-alarm-status-select', + templateUrl: './alarm-status-select.component.html', + styleUrls: ['./alarm-status-select.component.scss'], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => AlarmStatusSelectComponent), + multi: true + }] +}) + +export class AlarmStatusSelectComponent extends PageComponent implements OnInit, ControlValueAccessor { + + public alarmStatusGroup: FormGroup; + + private propagateChange = null; + + readonly alarmStatus = AlarmStatus; + readonly alarmStatusTranslations = alarmStatusTranslations; + + constructor(private fb: FormBuilder, + private destroyRef: DestroyRef) { + super(); + } + + ngOnInit(): void { + this.alarmStatusGroup = this.fb.group({ + alarmStatus: [null, []] + }); + + this.alarmStatusGroup.get('alarmStatus').valueChanges.pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe((value) => { + this.propagateChange(value); + }); + } + + setDisabledState(isDisabled: boolean): void { + if (isDisabled) { + this.alarmStatusGroup.disable({emitEvent: false}); + } else { + this.alarmStatusGroup.enable({emitEvent: false}); + } + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(_fn: any): void { + } + + writeValue(value: Array): void { + this.alarmStatusGroup.get('alarmStatus').patchValue(value, {emitEvent: false}); + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/arguments-map-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/common/arguments-map-config.component.html new file mode 100644 index 0000000000..dcf7bd896e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/arguments-map-config.component.html @@ -0,0 +1,129 @@ + +
    + +
    + + +
    + +
    + {{argumentControl.get('name').value}}. +
    + + rule-node-config.argument-source-field-input + + + {{ argumentTypeMap.get(argumentControl.get('type').value)?.name | translate }} + + + {{ argumentTypeMap.get(argument).name | translate }} + + {{ argumentTypeMap.get(argument).description | translate }} + + + + + rule-node-config.argument-source-field-input-required + + +
    + + rule-node-config.argument-key-field-input + + + help + + + rule-node-config.argument-key-field-input-required + + + + rule-node-config.constant-value-field-input + + + rule-node-config.constant-value-field-input-required + + + + rule-node-config.default-value-field-input + + +
    + + rule-node-config.attribute-scope-field-input + + + {{ attributeScopeMap.get(scope) | translate }} + + + + rule-node-config.attribute-scope-field-input-required + + +
    + +
    +
    +
    +
    +
    +
    + rule-node-config.no-arguments-prompt +
    + +
    diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessorFactory.java b/ui-ngx/src/app/modules/home/components/rule-node/common/arguments-map-config.component.scss similarity index 60% rename from application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessorFactory.java rename to ui-ngx/src/app/modules/home/components/rule-node/common/arguments-map-config.component.scss index 150d21d1d0..9f6893b5df 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessorFactory.java +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/arguments-map-config.component.scss @@ -13,14 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.edge.rpc.processor.asset; - -import org.springframework.stereotype.Component; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessorFactory; - -@Component -@TbCoreComponent -public class AssetEdgeProcessorFactory extends BaseEdgeProcessorFactory { +:host { + .mat-mdc-list-item.tb-argument { + border: solid rgba(0, 0, 0, .25) 1px; + border-radius: 4px; + padding: 10px 0; + margin-bottom: 16px; + } + .arguments-list { + padding: 0px; + } } diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/arguments-map-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/common/arguments-map-config.component.ts new file mode 100644 index 0000000000..3e6d5826f8 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/arguments-map-config.component.ts @@ -0,0 +1,238 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, DestroyRef, forwardRef, Input, OnInit } from '@angular/core'; +import { + ControlValueAccessor, + FormArray, + FormBuilder, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + Validator, + Validators +} from '@angular/forms'; +import { PageComponent } from '@shared/public-api'; +import { CdkDragDrop } from '@angular/cdk/drag-drop'; +import { + ArgumentName, + ArgumentType, + ArgumentTypeMap, + AttributeScope, + AttributeScopeMap, + MathFunction, + MathFunctionMap +} from './../rule-node-config.models'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; + +@Component({ + selector: 'tb-arguments-map-config', + templateUrl: './arguments-map-config.component.html', + styleUrls: ['./arguments-map-config.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ArgumentsMapConfigComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => ArgumentsMapConfigComponent), + multi: true, + } + ] +}) +export class ArgumentsMapConfigComponent extends PageComponent implements ControlValueAccessor, OnInit, Validator { + + @Input() disabled: boolean; + + private functionValue: MathFunction; + + get function(): MathFunction { + return this.functionValue; + } + + @Input() + set function(funcName: MathFunction) { + if (funcName && this.functionValue !== funcName) { + this.functionValue = funcName; + this.setupArgumentsFormGroup(true); + } + } + + maxArgs = 16; + minArgs = 1; + displayArgumentName = false; + + mathFunctionMap = MathFunctionMap; + ArgumentType = ArgumentType; + + argumentsFormGroup: FormGroup; + + attributeScopeMap = AttributeScopeMap; + argumentTypeMap = ArgumentTypeMap; + arguments = Object.values(ArgumentType); + attributeScope = Object.values(AttributeScope); + + private propagateChange = null; + + constructor(private fb: FormBuilder, + private destroyRef: DestroyRef) { + super(); + } + + ngOnInit(): void { + this.argumentsFormGroup = this.fb.group({ + arguments: this.fb.array([]) + }); + + this.argumentsFormGroup.valueChanges.pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe(() => { + this.updateModel(); + }); + + this.setupArgumentsFormGroup(); + } + + public onDrop(event: CdkDragDrop) { + const columnsFormArray = this.argumentsFormArray; + const columnForm = columnsFormArray.at(event.previousIndex); + columnsFormArray.removeAt(event.previousIndex); + columnsFormArray.insert(event.currentIndex, columnForm); + this.updateArgumentNames(); + } + + get argumentsFormArray(): FormArray { + return this.argumentsFormGroup.get('arguments') as FormArray; + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(_fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.argumentsFormGroup.disable({emitEvent: false}); + } else { + this.argumentsFormGroup.enable({emitEvent: false}); + this.argumentsFormArray.controls + .forEach((control: FormGroup) => this.updateArgumentControlValidators(control)); + } + } + + writeValue(argumentsList: Array): void { + const argumentsControls: Array = []; + if (argumentsList) { + argumentsList.forEach((property, index) => { + argumentsControls.push(this.createArgumentControl(property, index)); + }); + } + this.argumentsFormGroup.setControl('arguments', this.fb.array(argumentsControls), {emitEvent: false}); + this.setupArgumentsFormGroup(); + } + + + public removeArgument(index: number) { + this.argumentsFormArray.removeAt(index); + this.updateArgumentNames(); + } + + public addArgument(emitEvent = true) { + const argumentsFormArray = this.argumentsFormArray; + const argumentControl = this.createArgumentControl(null, argumentsFormArray.length); + argumentsFormArray.push(argumentControl, {emitEvent}); + } + + public validate() { + if (!this.argumentsFormGroup.valid) { + return { + argumentsRequired: true + }; + } + return null; + } + + private setupArgumentsFormGroup(emitEvent = false) { + if (this.function) { + this.maxArgs = this.mathFunctionMap.get(this.function).maxArgs; + this.minArgs = this.mathFunctionMap.get(this.function).minArgs; + this.displayArgumentName = this.function === MathFunction.CUSTOM; + } + if (this.argumentsFormGroup) { + this.argumentsFormGroup.get('arguments').setValidators([Validators.minLength(this.minArgs), Validators.maxLength(this.maxArgs)]); + while (this.argumentsFormArray.length > this.maxArgs) { + this.removeArgument(this.maxArgs - 1); + } + while (this.argumentsFormArray.length < this.minArgs) { + this.addArgument(emitEvent); + } + this.argumentsFormGroup.get('arguments').updateValueAndValidity({emitEvent: false}); + } + } + + private createArgumentControl(property: any, index: number): FormGroup { + const argumentControl = this.fb.group({ + type: [property?.type, [Validators.required]], + key: [property?.key, [Validators.required]], + name: [ArgumentName[index], [Validators.required]], + attributeScope: [property?.attributeScope ?? AttributeScope.SERVER_SCOPE, [Validators.required]], + defaultValue: [property?.defaultValue ? property?.defaultValue : null] + }); + this.updateArgumentControlValidators(argumentControl); + argumentControl.get('type').valueChanges.pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe(() => { + this.updateArgumentControlValidators(argumentControl); + argumentControl.get('attributeScope').updateValueAndValidity({emitEvent: false}); + argumentControl.get('defaultValue').updateValueAndValidity({emitEvent: false}); + }); + return argumentControl; + } + + private updateArgumentControlValidators(control: FormGroup) { + const argumentType: ArgumentType = control.get('type').value; + if (argumentType === ArgumentType.ATTRIBUTE) { + control.get('attributeScope').enable({emitEvent: false}); + } else { + control.get('attributeScope').disable({emitEvent: false}); + } + if (argumentType && argumentType !== ArgumentType.CONSTANT) { + control.get('defaultValue').enable({emitEvent: false}); + } else { + control.get('defaultValue').disable({emitEvent: false}); + } + } + + private updateArgumentNames() { + this.argumentsFormArray.controls.forEach((argumentControl, argumentIndex) => { + argumentControl.get('name').setValue(ArgumentName[argumentIndex]); + }); + } + + private updateModel() { + const argumentsForm = this.argumentsFormArray.value; + if (!argumentsForm.length || !this.argumentsFormGroup.valid) { + this.propagateChange(null); + } else { + this.propagateChange(argumentsForm); + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/common-rule-node-config.module.ts b/ui-ngx/src/app/modules/home/components/rule-node/common/common-rule-node-config.module.ts new file mode 100644 index 0000000000..b6370be318 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/common-rule-node-config.module.ts @@ -0,0 +1,83 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@shared/public-api'; +import { HomeComponentsModule } from '@home/components/public-api'; +import { KvMapConfigComponent } from './kv-map-config.component'; +import { DeviceRelationsQueryConfigComponent } from './device-relations-query-config.component'; +import { RelationsQueryConfigComponent } from './relations-query-config.component'; +import { MessageTypesConfigComponent } from './message-types-config.component'; +import { CredentialsConfigComponent } from './credentials-config.component'; +import { ArgumentsMapConfigComponent } from './arguments-map-config.component'; +import { MathFunctionAutocompleteComponent } from './math-function-autocomplete.component'; +import { OutputMessageTypeAutocompleteComponent } from './output-message-type-autocomplete.component'; +import { KvMapConfigOldComponent } from './kv-map-config-old.component'; +import { MsgMetadataChipComponent } from './msg-metadata-chip.component'; +import { SvMapConfigComponent } from './sv-map-config.component'; +import { RelationsQueryConfigOldComponent } from './relations-query-config-old.component'; +import { SelectAttributesComponent } from './select-attributes.component'; +import { AlarmStatusSelectComponent } from './alarm-status-select.component'; +import { ExampleHintComponent } from './example-hint.component'; +import { TimeUnitInputComponent } from './time-unit-input.component'; + +@NgModule({ + declarations: [ + KvMapConfigComponent, + DeviceRelationsQueryConfigComponent, + RelationsQueryConfigComponent, + MessageTypesConfigComponent, + CredentialsConfigComponent, + ArgumentsMapConfigComponent, + MathFunctionAutocompleteComponent, + OutputMessageTypeAutocompleteComponent, + KvMapConfigOldComponent, + MsgMetadataChipComponent, + SvMapConfigComponent, + RelationsQueryConfigOldComponent, + SelectAttributesComponent, + AlarmStatusSelectComponent, + ExampleHintComponent, + TimeUnitInputComponent + ], + imports: [ + CommonModule, + SharedModule, + HomeComponentsModule + ], + exports: [ + KvMapConfigComponent, + DeviceRelationsQueryConfigComponent, + RelationsQueryConfigComponent, + MessageTypesConfigComponent, + CredentialsConfigComponent, + ArgumentsMapConfigComponent, + MathFunctionAutocompleteComponent, + OutputMessageTypeAutocompleteComponent, + KvMapConfigOldComponent, + MsgMetadataChipComponent, + SvMapConfigComponent, + RelationsQueryConfigOldComponent, + SelectAttributesComponent, + AlarmStatusSelectComponent, + ExampleHintComponent, + TimeUnitInputComponent + ] +}) + +export class CommonRuleNodeConfigModule { +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/credentials-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/common/credentials-config.component.html new file mode 100644 index 0000000000..d4a168aa53 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/credentials-config.component.html @@ -0,0 +1,95 @@ + +
    + + + rule-node-config.credentials + + {{ credentialsTypeTranslationsMap.get(credentialsConfigFormGroup.get('type').value) | translate }} + + + + + rule-node-config.credentials-type + + + {{ credentialsTypeTranslationsMap.get(credentialsType) | translate }} + + + + {{ 'rule-node-config.credentials-type-required' | translate }} + + +
    + + + + + rule-node-config.username + + + {{ 'rule-node-config.username-required' | translate }} + + + + rule-node-config.password + + + + {{ 'rule-node-config.password-required' | translate }} + + + + +
    {{ 'rule-node-config.credentials-pem-hint' | translate }}
    + + + + + + + + rule-node-config.private-key-password + + + +
    +
    +
    +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/credentials-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/common/credentials-config.component.ts new file mode 100644 index 0000000000..95ed59683d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/credentials-config.component.ts @@ -0,0 +1,244 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, DestroyRef, forwardRef, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; +import { + ControlValueAccessor, + FormBuilder, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + ValidationErrors, + Validator, + ValidatorFn, + Validators +} from '@angular/forms'; +import { isDefinedAndNotNull } from '@core/public-api'; +import { PageComponent } from '@shared/public-api'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { credentialsType, credentialsTypes, credentialsTypeTranslations } from '../rule-node-config.models'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; + +interface CredentialsConfig { + type: credentialsType; + username?: string; + password?: string; + caCert?: string; + caCertFileName?: string; + privateKey?: string; + privateKeyFileName?: string; + cert?: string; + certFileName?: string; +} + +@Component({ + selector: 'tb-credentials-config', + templateUrl: './credentials-config.component.html', + styleUrls: [], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => CredentialsConfigComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => CredentialsConfigComponent), + multi: true, + } + ] +}) +export class CredentialsConfigComponent extends PageComponent implements ControlValueAccessor, OnInit, Validator, OnChanges { + + credentialsConfigFormGroup: FormGroup; + + private requiredValue: boolean; + + get required(): boolean { + return this.requiredValue; + } + + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disableCertPemCredentials = false; + + @Input() + passwordFieldRequired = true; + + allCredentialsTypes = credentialsTypes; + credentialsTypeTranslationsMap = credentialsTypeTranslations; + + private propagateChange = (_: any) => {}; + + constructor(private fb: FormBuilder, + private destroyRef: DestroyRef) { + super(); + } + + ngOnInit(): void { + this.credentialsConfigFormGroup = this.fb.group( + { + type: [null, [Validators.required]], + username: [null, []], + password: [null, []], + caCert: [null, []], + caCertFileName: [null, []], + privateKey: [null, []], + privateKeyFileName: [null, []], + cert: [null, []], + certFileName: [null, []] + } + ); + this.credentialsConfigFormGroup.valueChanges.pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe(() => { + this.updateView(); + }); + this.credentialsConfigFormGroup.get('type').valueChanges.pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe(() => { + this.credentialsTypeChanged(); + }); + } + + ngOnChanges(changes: SimpleChanges): void { + for (const propName of Object.keys(changes)) { + const change = changes[propName]; + if (!change.firstChange && change.currentValue !== change.previousValue) { + if (change.currentValue && propName === 'disableCertPemCredentials') { + const credentialsTypeValue: credentialsType = this.credentialsConfigFormGroup.get('type').value; + if (credentialsTypeValue === 'cert.PEM') { + setTimeout(() => { + this.credentialsConfigFormGroup.get('type').patchValue('anonymous', {emitEvent: true}); + }); + } + } + } + } + } + + writeValue(credentials: CredentialsConfig | null): void { + if (isDefinedAndNotNull(credentials)) { + this.credentialsConfigFormGroup.reset(credentials, {emitEvent: false}); + this.updateValidators(); + } + } + + setDisabledState(isDisabled: boolean): void { + if (isDisabled) { + this.credentialsConfigFormGroup.disable({emitEvent: false}); + } else { + this.credentialsConfigFormGroup.enable({emitEvent: false}); + this.updateValidators(); + } + } + + updateView() { + let credentialsConfigValue = this.credentialsConfigFormGroup.value; + const credentialsTypeValue: credentialsType = credentialsConfigValue.type; + switch (credentialsTypeValue) { + case 'anonymous': + credentialsConfigValue = { + type: credentialsTypeValue + }; + break; + case 'basic': + credentialsConfigValue = { + type: credentialsTypeValue, + username: credentialsConfigValue.username, + password: credentialsConfigValue.password, + }; + break; + case 'cert.PEM': + delete credentialsConfigValue.username; + break; + } + this.propagateChange(credentialsConfigValue); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(): void { + } + + public validate() { + return this.credentialsConfigFormGroup.valid ? null : { + credentialsConfig: { + valid: false, + }, + }; + } + + credentialsTypeChanged(): void { + this.credentialsConfigFormGroup.patchValue({ + username: null, + password: null, + caCert: null, + caCertFileName: null, + privateKey: null, + privateKeyFileName: null, + cert: null, + certFileName: null, + }); + this.updateValidators(); + } + + protected updateValidators(emitEvent: boolean = false) { + const credentialsTypeValue: credentialsType = this.credentialsConfigFormGroup.get('type').value; + if (emitEvent) { + this.credentialsConfigFormGroup.reset({type: credentialsTypeValue}, {emitEvent: false}); + } + this.credentialsConfigFormGroup.setValidators([]); + this.credentialsConfigFormGroup.get('username').setValidators([]); + this.credentialsConfigFormGroup.get('password').setValidators([]); + switch (credentialsTypeValue) { + case 'anonymous': + break; + case 'basic': + this.credentialsConfigFormGroup.get('username').setValidators([Validators.required]); + this.credentialsConfigFormGroup.get('password').setValidators(this.passwordFieldRequired ? [Validators.required] : []); + break; + case 'cert.PEM': + this.credentialsConfigFormGroup.setValidators([this.requiredFilesSelected( + Validators.required, + [['caCert', 'caCertFileName'], ['privateKey', 'privateKeyFileName', 'cert', 'certFileName']] + )]); + break; + } + this.credentialsConfigFormGroup.get('username').updateValueAndValidity({emitEvent}); + this.credentialsConfigFormGroup.get('password').updateValueAndValidity({emitEvent}); + this.credentialsConfigFormGroup.updateValueAndValidity({emitEvent}); + } + + private requiredFilesSelected(validator: ValidatorFn, + requiredFieldsSet: string[][] = null) { + return (group: FormGroup): ValidationErrors | null => { + if (!requiredFieldsSet) { + requiredFieldsSet = [Object.keys(group.controls)]; + } + const allRequiredFilesSelected = group?.controls && + requiredFieldsSet.some(arrFields => arrFields.every(k => !validator(group.controls[k]))); + + return allRequiredFilesSelected ? null : {notAllRequiredFilesSelected: true}; + }; + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/device-relations-query-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/common/device-relations-query-config.component.html new file mode 100644 index 0000000000..b1f33c1937 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/device-relations-query-config.component.html @@ -0,0 +1,66 @@ + +
    +
    + + relation.direction + + + {{ directionTypeTranslations.get(type) | translate }} rule-node-config.relations-query-config-direction-suffix + + + + + rule-node-config.max-relation-level + + + {{ 'rule-node-config.max-relation-level-error' | translate }} + + + {{ 'rule-node-config.max-relation-level-invalid' | translate }} + + +
    +
    + + {{ 'alias.last-level-relation' | translate }} + +
    + + + + help + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/device-relations-query-config.component.scss b/ui-ngx/src/app/modules/home/components/rule-node/common/device-relations-query-config.component.scss new file mode 100644 index 0000000000..95cfd96f20 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/device-relations-query-config.component.scss @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + .last-level-slide-toggle { + margin: 8px 0 24px; + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/device-relations-query-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/common/device-relations-query-config.component.ts new file mode 100644 index 0000000000..bc9f524a75 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/device-relations-query-config.component.ts @@ -0,0 +1,112 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, DestroyRef, forwardRef, Input, OnInit } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { PageComponent } from '@shared/components/page.component'; +import { EntitySearchDirection, entitySearchDirectionTranslations } from '@app/shared/models/relation.models'; +import { EntityType } from '@shared/models/entity-type.models'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; + +interface DeviceRelationsQuery { + fetchLastLevelOnly: boolean; + direction: EntitySearchDirection; + maxLevel?: number; + relationType?: string; + deviceTypes: string[]; +} + +@Component({ + selector: 'tb-device-relations-query-config', + templateUrl: './device-relations-query-config.component.html', + styleUrls: ['./device-relations-query-config.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DeviceRelationsQueryConfigComponent), + multi: true + } + ] +}) +export class DeviceRelationsQueryConfigComponent extends PageComponent implements ControlValueAccessor, OnInit { + + @Input() disabled: boolean; + + private requiredValue: boolean; + + get required(): boolean { + return this.requiredValue; + } + + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + directionTypes: Array = Object.values(EntitySearchDirection); + directionTypeTranslations = entitySearchDirectionTranslations; + + entityType = EntityType; + + deviceRelationsQueryFormGroup: FormGroup; + + private propagateChange = null; + + constructor(private fb: FormBuilder, + private destroyRef: DestroyRef) { + super(); + } + + ngOnInit(): void { + this.deviceRelationsQueryFormGroup = this.fb.group({ + fetchLastLevelOnly: [false, []], + direction: [null, [Validators.required]], + maxLevel: [null, [Validators.min(1)]], + relationType: [null], + deviceTypes: [null, [Validators.required]] + }); + this.deviceRelationsQueryFormGroup.valueChanges.pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe((query: DeviceRelationsQuery) => { + if (this.deviceRelationsQueryFormGroup.valid) { + this.propagateChange(query); + } else { + this.propagateChange(null); + } + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(_fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.deviceRelationsQueryFormGroup.disable({emitEvent: false}); + } else { + this.deviceRelationsQueryFormGroup.enable({emitEvent: false}); + } + } + + writeValue(query: DeviceRelationsQuery): void { + this.deviceRelationsQueryFormGroup.reset(query, {emitEvent: false}); + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/example-hint.component.html b/ui-ngx/src/app/modules/home/components/rule-node/common/example-hint.component.html new file mode 100644 index 0000000000..0981daa5cb --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/example-hint.component.html @@ -0,0 +1,29 @@ + +
    +
    +
    +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/example-hint.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/common/example-hint.component.ts new file mode 100644 index 0000000000..1bcb4ea714 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/example-hint.component.ts @@ -0,0 +1,32 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'tb-example-hint', + templateUrl: './example-hint.component.html', + styleUrls: [] +}) +export class ExampleHintComponent { + @Input() hintText: string; + + @Input() popupHelpLink: string; + + @Input() textAlign: string = 'left'; +} + + diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config-old.component.html b/ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config-old.component.html new file mode 100644 index 0000000000..6678008fd0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config-old.component.html @@ -0,0 +1,70 @@ + +
    +
    + {{ keyText | translate }} + {{ valText | translate }} + +
    +
    +
    + + + + {{ keyRequiredText | translate }} + + + + + + {{ valRequiredText | translate }} + + + +
    +
    +
    + +
    + +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config-old.component.scss b/ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config-old.component.scss new file mode 100644 index 0000000000..fad3641ba4 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config-old.component.scss @@ -0,0 +1,59 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + .tb-kv-map-config { + margin-bottom: 16px; + + .header { + padding-left: 5px; + padding-right: 5px; + padding-bottom: 5px; + + .cell { + padding-left: 5px; + padding-right: 5px; + color: #757575; + font-size: 12px; + font-weight: 700; + white-space: nowrap; + } + + .tb-required::after { + color: #757575; + font-size: 12px; + font-weight: 700; + } + } + + .body { + padding-left: 5px; + padding-right: 5px; + padding-bottom: 0; + max-height: 300px; + overflow: auto; + + .cell { + padding-left: 5px; + padding-right: 5px; + } + } + + tb-error { + display: block; + margin-top: -12px; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config-old.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config-old.component.ts new file mode 100644 index 0000000000..1f7407c5e3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config-old.component.ts @@ -0,0 +1,193 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, DestroyRef, forwardRef, Injector, Input, OnInit } from '@angular/core'; +import { + AbstractControl, + ControlValueAccessor, + FormArray, + FormBuilder, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + NgControl, + Validator, + Validators +} from '@angular/forms'; +import { PageComponent } from '@shared/public-api'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { TranslateService } from '@ngx-translate/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; + +@Component({ + selector: 'tb-kv-map-config-old', + templateUrl: './kv-map-config-old.component.html', + styleUrls: ['./kv-map-config-old.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => KvMapConfigOldComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => KvMapConfigOldComponent), + multi: true, + } + ] +}) +export class KvMapConfigOldComponent extends PageComponent implements ControlValueAccessor, OnInit, Validator { + + @Input() disabled: boolean; + + @Input() uniqueKeyValuePairValidator: boolean; + + @Input() requiredText: string; + + @Input() keyText: string; + + @Input() keyRequiredText: string; + + @Input() valText: string; + + @Input() valRequiredText: string; + + @Input() hintText: string; + + private requiredValue: boolean; + + get required(): boolean { + return this.requiredValue; + } + + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + kvListFormGroup: FormGroup; + + ngControl: NgControl; + + private propagateChange = null; + + constructor(public translate: TranslateService, + private injector: Injector, + private fb: FormBuilder, + private destroyRef: DestroyRef) { + super(); + } + + ngOnInit(): void { + this.ngControl = this.injector.get(NgControl); + if (this.ngControl != null) { + this.ngControl.valueAccessor = this; + } + this.kvListFormGroup = this.fb.group({ + keyVals: this.fb.array([]) + }); + + this.kvListFormGroup.valueChanges.pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe(() => { + this.updateModel(); + }); + } + + keyValsFormArray(): FormArray { + return this.kvListFormGroup.get('keyVals') as FormArray; + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(_fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.kvListFormGroup.disable({emitEvent: false}); + } else { + this.kvListFormGroup.enable({emitEvent: false}); + } + } + + writeValue(keyValMap: { [key: string]: string }): void { + const keyValsControls: Array = []; + if (keyValMap) { + for (const property of Object.keys(keyValMap)) { + if (Object.prototype.hasOwnProperty.call(keyValMap, property)) { + keyValsControls.push(this.fb.group({ + key: [property, [Validators.required]], + value: [keyValMap[property], [Validators.required]] + })); + } + } + } + this.kvListFormGroup.setControl('keyVals', this.fb.array(keyValsControls), {emitEvent: false}); + } + + public removeKeyVal(index: number) { + (this.kvListFormGroup.get('keyVals') as FormArray).removeAt(index); + } + + public addKeyVal() { + const keyValsFormArray = this.kvListFormGroup.get('keyVals') as FormArray; + keyValsFormArray.push(this.fb.group({ + key: ['', [Validators.required]], + value: ['', [Validators.required]] + })); + } + + public validate() { + const kvList: { key: string; value: string }[] = this.kvListFormGroup.get('keyVals').value; + if (!kvList.length && this.required) { + return { + kvMapRequired: true + }; + } + if (!this.kvListFormGroup.valid) { + return { + kvFieldsRequired: true + }; + } + if (this.uniqueKeyValuePairValidator) { + for (const kv of kvList) { + if (kv.key === kv.value) { + return { + uniqueKeyValuePair: true + }; + } + } + } + return null; + } + + private updateModel() { + const kvList: { key: string; value: string }[] = this.kvListFormGroup.get('keyVals').value; + if (this.required && !kvList.length || !this.kvListFormGroup.valid) { + this.propagateChange(null); + } else { + const keyValMap: { [key: string]: string } = {}; + kvList.forEach((entry) => { + keyValMap[entry.key] = entry.value; + }); + this.propagateChange(keyValMap); + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config.component.html new file mode 100644 index 0000000000..f2b4a5ee2a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config.component.html @@ -0,0 +1,72 @@ + +
    +
    +
    {{ labelText }}
    +
    + {{ requiredText }} +
    +
    + rule-node-config.map-fields-required +
    +
    + {{ 'rule-node-config.key-val.unique-key-value-pair-error' | translate: + { + valText: valText, + keyText: keyText + } }} +
    +
    +
    +
    +
    +
    {{ keyText }}
    +
    {{ valText }}
    +
    +
    +
    +
    + + + + + + +
    + +
    +
    +
    +
    +
    +
    + +
    + +
    diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessorFactory.java b/ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config.component.scss similarity index 60% rename from application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessorFactory.java rename to ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config.component.scss index 85327bfc1f..132a1f882c 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessorFactory.java +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config.component.scss @@ -13,14 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.edge.rpc.processor.device; - -import org.springframework.stereotype.Component; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessorFactory; - -@Component -@TbCoreComponent -public class DeviceEdgeProcessorFactory extends BaseEdgeProcessorFactory { +:host { + .field-space { + flex: 1 1 50%; + } + .actions-header { + width: 40px + } } diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config.component.ts new file mode 100644 index 0000000000..f46b5e9923 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config.component.ts @@ -0,0 +1,235 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, DestroyRef, forwardRef, Injector, Input, OnInit } from '@angular/core'; +import { + AbstractControl, + ControlValueAccessor, + FormArray, + FormBuilder, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + NgControl, + ValidationErrors, + Validator, + ValidatorFn, + Validators +} from '@angular/forms'; +import { coerceBoolean } from '@shared/public-api'; +import { isEqual } from '@core/public-api'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; + +@Component({ + selector: 'tb-kv-map-config', + templateUrl: './kv-map-config.component.html', + styleUrls: ['./kv-map-config.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => KvMapConfigComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => KvMapConfigComponent), + multi: true, + } + ] +}) +export class KvMapConfigComponent implements ControlValueAccessor, OnInit, Validator { + + private propagateChange: (value: any) => void = () => {}; + + kvListFormGroup: FormGroup; + ngControl: NgControl; + + @Input() + @coerceBoolean() + disabled = false; + + @Input() + @coerceBoolean() + uniqueKeyValuePairValidator = false; + + @Input() labelText: string; + + @Input() requiredText: string; + + @Input() keyText: string; + + @Input() keyRequiredText: string; + + @Input() valText: string; + + @Input() valRequiredText: string; + + @Input() hintText: string; + + @Input() popupHelpLink: string; + + @Input() + @coerceBoolean() + required = false; + + constructor(private injector: Injector, + private fb: FormBuilder, + private destroyRef: DestroyRef) { + } + + ngOnInit(): void { + this.ngControl = this.injector.get(NgControl); + if (this.ngControl != null) { + this.ngControl.valueAccessor = this; + } + + this.kvListFormGroup = this.fb.group({ + keyVals: this.fb.array([]) + }, {validators: [this.propagateNestedErrors, this.oneMapRequiredValidator]}); + + this.kvListFormGroup.valueChanges + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(() => { + this.updateModel(); + }); + } + + keyValsFormArray(): FormArray { + return this.kvListFormGroup.get('keyVals') as FormArray; + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(_fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.kvListFormGroup.disable({emitEvent: false}); + } else { + this.kvListFormGroup.enable({emitEvent: false}); + } + } + + private duplicateValuesValidator: ValidatorFn = (control: FormGroup): ValidationErrors | null => + control.controls.key.value === control.controls.value.value + ? control.controls.key.value && control.controls.value.value ? { uniqueKeyValuePair: true } : null + : null; + + private oneMapRequiredValidator: ValidatorFn = (control: FormGroup): ValidationErrors | null => control.get('keyVals').value.length; + + + private propagateNestedErrors: ValidatorFn = (controls: FormArray | FormGroup | AbstractControl): ValidationErrors | null => { + if (this.kvListFormGroup && this.kvListFormGroup.get('keyVals') && this.kvListFormGroup.get('keyVals')?.status === 'VALID') { + return null; + } + const errors = {}; + if (this.kvListFormGroup) { + this.kvListFormGroup.setErrors(null); + } + if (controls instanceof FormArray || controls instanceof FormGroup) { + if (controls.errors) { + for (const errorKey of Object.keys(controls.errors)) { + errors[errorKey] = true; + } + } + for (const control of Object.keys(controls.controls)) { + const innerErrors = this.propagateNestedErrors(controls.controls[control]); + if (innerErrors && Object.keys(innerErrors).length) { + for (const errorKey of Object.keys(innerErrors)) { + errors[errorKey] = true; + } + } + } + return errors; + } else { + if (controls.errors) { + for (const errorKey of Object.keys(controls.errors)) { + errors[errorKey] = true; + } + } + } + + return !isEqual(errors, {}) ? errors : null; + }; + + writeValue(keyValMap: { [key: string]: string }): void { + const keyValuesData = Object.keys(keyValMap).map(key => ({key, value: keyValMap[key]})); + if (this.keyValsFormArray().length === keyValuesData.length) { + this.keyValsFormArray().patchValue(keyValuesData, {emitEvent: false}); + } else { + const keyValsControls: Array = []; + keyValuesData.forEach(data => { + keyValsControls.push(this.fb.group({ + key: [data.key, [Validators.required, Validators.pattern(/(?:.|\s)*\S(&:.|\s)*/)]], + value: [data.value, [Validators.required, Validators.pattern(/(?:.|\s)*\S(&:.|\s)*/)]] + }, {validators: this.uniqueKeyValuePairValidator ? [this.duplicateValuesValidator] : []})); + }); + this.kvListFormGroup.setControl('keyVals', this.fb.array(keyValsControls, this.propagateNestedErrors), {emitEvent: false}); + } + } + + public removeKeyVal(index: number) { + this.keyValsFormArray().removeAt(index); + } + + public addKeyVal() { + this.keyValsFormArray().push(this.fb.group({ + key: ['', [Validators.required, Validators.pattern(/(?:.|\s)*\S(&:.|\s)*/)]], + value: ['', [Validators.required, Validators.pattern(/(?:.|\s)*\S(&:.|\s)*/)]] + }, {validators: this.uniqueKeyValuePairValidator ? [this.duplicateValuesValidator] : []})); + } + + public validate() { + const kvList: { key: string; value: string }[] = this.kvListFormGroup.get('keyVals').value; + if (!kvList.length && this.required) { + return { + kvMapRequired: true + }; + } + if (!this.kvListFormGroup.valid) { + return { + kvFieldsRequired: true + }; + } + if (this.uniqueKeyValuePairValidator) { + for (const kv of kvList) { + if (kv.key === kv.value) { + return { + uniqueKeyValuePair: true + }; + } + } + } + return null; + } + + private updateModel() { + const kvList: { key: string; value: string }[] = this.kvListFormGroup.get('keyVals').value; + if (this.required && !kvList.length || !this.kvListFormGroup.valid) { + this.propagateChange(null); + } else { + const keyValMap: { [key: string]: string } = {}; + kvList.forEach((entry) => { + keyValMap[entry.key] = entry.value; + }); + this.propagateChange(keyValMap); + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/math-function-autocomplete.component.html b/ui-ngx/src/app/modules/home/components/rule-node/common/math-function-autocomplete.component.html new file mode 100644 index 0000000000..eb122fe028 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/math-function-autocomplete.component.html @@ -0,0 +1,43 @@ + + + rule-node-config.functions-field-input + + + + + + + {{ option.description }} + + + + rule-node-config.no-option-found + + + diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/math-function-autocomplete.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/common/math-function-autocomplete.component.ts new file mode 100644 index 0000000000..fb34cf4418 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/math-function-autocomplete.component.ts @@ -0,0 +1,152 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { Observable } from 'rxjs'; +import { TranslateService } from '@ngx-translate/core'; +import { FunctionData, MathFunction, MathFunctionMap } from '../rule-node-config.models'; +import { map, tap } from 'rxjs/operators'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; + +@Component({ + selector: 'tb-math-function-autocomplete', + templateUrl: './math-function-autocomplete.component.html', + styleUrls: [], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => MathFunctionAutocompleteComponent), + multi: true + } + ] +}) +export class MathFunctionAutocompleteComponent implements ControlValueAccessor, OnInit { + + private requiredValue: boolean; + + get required(): boolean { + return this.requiredValue; + } + + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() disabled: boolean; + + @ViewChild('operationInput', {static: true}) operationInput: ElementRef; + + mathFunctionForm: UntypedFormGroup; + + modelValue: MathFunction | null; + + searchText = ''; + + filteredOptions: Observable; + + private dirty = false; + + private mathOperation = [...MathFunctionMap.values()]; + + private propagateChange = null; + + constructor(public translate: TranslateService, + private fb: UntypedFormBuilder) { + } + + ngOnInit(): void { + this.mathFunctionForm = this.fb.group({ + operation: [''] + }); + this.filteredOptions = this.mathFunctionForm.get('operation').valueChanges.pipe( + tap(value => { + let modelValue: MathFunction; + if (typeof value === 'string' && MathFunction[value]) { + modelValue = MathFunction[value]; + } else { + modelValue = null; + } + this.updateView(modelValue); + }), + map(value => { + this.searchText = value || ''; + return value ? this._filter(value) : this.mathOperation.slice(); + }), + ); + } + + private _filter(searchText: string) { + const filterValue = searchText.toLowerCase(); + + return this.mathOperation.filter(option => option.name.toLowerCase().includes(filterValue) + || option.value.toLowerCase().includes(filterValue)); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(_fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.mathFunctionForm.disable({emitEvent: false}); + } else { + this.mathFunctionForm.enable({emitEvent: false}); + } + } + + mathFunctionDisplayFn(value: MathFunction | null) { + if (value) { + const funcData = MathFunctionMap.get(value) + return funcData.value + ' | ' + funcData.name; + } + return ''; + } + + writeValue(value: MathFunction | null): void { + this.modelValue = value; + this.mathFunctionForm.get('operation').setValue(value, {emitEvent: false}); + this.dirty = true; + } + + updateView(value: MathFunction | null) { + if (this.modelValue !== value) { + this.modelValue = value; + this.propagateChange(this.modelValue); + } + } + + onFocus() { + if (this.dirty) { + this.mathFunctionForm.get('operation').updateValueAndValidity({onlySelf: true}); + this.dirty = false; + } + } + + clear() { + this.mathFunctionForm.get('operation').patchValue(''); + setTimeout(() => { + this.operationInput.nativeElement.blur(); + this.operationInput.nativeElement.focus(); + }, 0); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/message-types-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/common/message-types-config.component.html new file mode 100644 index 0000000000..174933847e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/message-types-config.component.html @@ -0,0 +1,71 @@ + + + {{ label }} + + + {{messageType.name}} + close + + + + + + + + +
    +
    + rule-node-config.no-message-types-found +
    + + + {{ 'rule-node-config.no-message-type-matching' | translate : + {messageType: truncate.transform(searchText, true, 6, '...')} + }} + + + + rule-node-config.create-new-message-type + +
    +
    +
    + help + + {{ 'rule-node-config.select-message-types-required' | translate }} + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/message-types-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/common/message-types-config.component.ts new file mode 100644 index 0000000000..c5d10a2d25 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/message-types-config.component.ts @@ -0,0 +1,239 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core'; +import { LinkLabel, MessageType, messageTypeNames, PageComponent, TruncatePipe } from '@shared/public-api'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { MatChipGrid, MatChipInputEvent } from '@angular/material/chips'; +import { MatAutocomplete, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; +import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes'; +import { Observable, of } from 'rxjs'; +import { map, mergeMap, share, startWith } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; + +@Component({ + selector: 'tb-message-types-config', + templateUrl: './message-types-config.component.html', + styleUrls: [], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => MessageTypesConfigComponent), + multi: true + } + ] +}) +export class MessageTypesConfigComponent extends PageComponent implements ControlValueAccessor, OnInit { + + messageTypeConfigForm: FormGroup; + + private requiredValue: boolean; + + get required(): boolean { + return this.requiredValue; + } + + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + label: string; + + @Input() + placeholder = 'rule-node-config.add-message-type'; + + @Input() + disabled: boolean; + + @ViewChild('chipList', {static: false}) chipList: MatChipGrid; + @ViewChild('messageTypeAutocomplete', {static: false}) matAutocomplete: MatAutocomplete; + @ViewChild('messageTypeInput', {static: false}) messageTypeInput: ElementRef; + + separatorKeysCodes = [ENTER, COMMA, SEMICOLON]; + + filteredMessageTypes: Observable>; + + messageTypes: Array = []; + + private messageTypesList: Array = []; + + searchText = ''; + + private propagateChange = (v: any) => { }; + + constructor(public translate: TranslateService, + public truncate: TruncatePipe, + private fb: FormBuilder) { + super(); + this.messageTypeConfigForm = this.fb.group({ + messageType: [null] + }); + for (const type of Object.keys(MessageType)) { + this.messageTypesList.push( + { + name: messageTypeNames.get(MessageType[type]), + value: type + } + ); + } + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(_fn: any): void { + } + + ngOnInit() { + this.filteredMessageTypes = this.messageTypeConfigForm.get('messageType').valueChanges + .pipe( + startWith(''), + map((value) => value ? value : ''), + mergeMap(name => this.fetchMessageTypes(name)), + share() + ); + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.messageTypeConfigForm.disable({emitEvent: false}); + } else { + this.messageTypeConfigForm.enable({emitEvent: false}); + } + } + + writeValue(value: Array | null): void { + this.searchText = ''; + this.messageTypes.length = 0; + if (value) { + value.forEach((type: string) => { + const found = this.messageTypesList.find((messageType => messageType.value === type)); + if (found) { + this.messageTypes.push({ + name: found.name, + value: found.value + }); + } else { + this.messageTypes.push({ + name: type, + value: type + }); + } + }); + } + } + + displayMessageTypeFn(messageType?: LinkLabel): string | undefined { + return messageType ? messageType.name : undefined; + } + + textIsNotEmpty(text: string): boolean { + return text && text.length > 0; + } + + createMessageType($event: Event, value: string) { + $event.preventDefault(); + this.transformMessageType(value); + } + + add(event: MatChipInputEvent): void { + this.transformMessageType(event.value); + } + + private fetchMessageTypes(searchText?: string): Observable> { + this.searchText = searchText; + if (this.searchText && this.searchText.length) { + const search = this.searchText.toUpperCase(); + return of(this.messageTypesList.filter(messageType => messageType.name.toUpperCase().includes(search))); + } else { + return of(this.messageTypesList); + } + } + + private transformMessageType(value: string) { + if ((value || '').trim()) { + let newMessageType: LinkLabel; + const messageTypeName = value.trim(); + const existingMessageType = this.messageTypesList.find(messageType => messageType.name === messageTypeName); + if (existingMessageType) { + newMessageType = { + name: existingMessageType.name, + value: existingMessageType.value + }; + } else { + newMessageType = { + name: messageTypeName, + value: messageTypeName + }; + } + if (newMessageType) { + this.addMessageType(newMessageType); + } + } + this.clear(''); + } + + remove(messageType: LinkLabel) { + const index = this.messageTypes.indexOf(messageType); + if (index >= 0) { + this.messageTypes.splice(index, 1); + this.updateModel(); + } + } + + selected(event: MatAutocompleteSelectedEvent): void { + this.addMessageType(event.option.value); + this.clear(''); + } + + addMessageType(messageType: LinkLabel): void { + const index = this.messageTypes.findIndex(existingMessageType => existingMessageType.value === messageType.value); + if (index === -1) { + this.messageTypes.push(messageType); + this.updateModel(); + } + } + + onFocus() { + this.messageTypeConfigForm.get('messageType').updateValueAndValidity({onlySelf: true, emitEvent: true}); + } + + clear(value: string = '') { + this.messageTypeInput.nativeElement.value = value; + this.messageTypeConfigForm.get('messageType').patchValue(null, {emitEvent: true}); + setTimeout(() => { + this.messageTypeInput.nativeElement.blur(); + this.messageTypeInput.nativeElement.focus(); + }, 0); + } + + private updateModel() { + const value = this.messageTypes.map((messageType => messageType.value)); + if (this.required) { + this.chipList.errorState = !value.length; + this.propagateChange(value.length > 0 ? value : null); + } else { + this.chipList.errorState = false; + this.propagateChange(value); + } + } + +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/msg-metadata-chip.component.html b/ui-ngx/src/app/modules/home/components/rule-node/common/msg-metadata-chip.component.html new file mode 100644 index 0000000000..d3fcba933c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/msg-metadata-chip.component.html @@ -0,0 +1,26 @@ + +
    +
    {{ labelText }}
    + + {{ option.name }} + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/msg-metadata-chip.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/common/msg-metadata-chip.component.ts new file mode 100644 index 0000000000..a8e00a68e4 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/msg-metadata-chip.component.ts @@ -0,0 +1,90 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, DestroyRef, forwardRef, Input, OnInit } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { FetchTo, FetchToTranslation } from '../rule-node-config.models'; +import { TranslateService } from '@ngx-translate/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; + +@Component({ + selector: 'tb-msg-metadata-chip', + templateUrl: './msg-metadata-chip.component.html', + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => MsgMetadataChipComponent), + multi: true + }] +}) + +export class MsgMetadataChipComponent implements OnInit, ControlValueAccessor { + + @Input() labelText: string; + @Input() translation: Map = FetchToTranslation; + + private propagateChange: (value: any) => void = () => {}; + + public chipControlGroup: FormGroup; + public selectOptions = []; + + constructor(private fb: FormBuilder, + private translate: TranslateService, + private destroyRef: DestroyRef) {} + + ngOnInit(): void { + this.initOptions(); + this.chipControlGroup = this.fb.group({ + chipControl: [null, []] + }); + + this.chipControlGroup.get('chipControl').valueChanges.pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe((value) => { + if (value) { + this.propagateChange(value); + } + } + ); + } + + initOptions() { + for (const key of this.translation.keys()) { + this.selectOptions.push({ + value: key, + name: this.translate.instant(this.translation.get(key)) + }); + } + } + + writeValue(value: string | null): void { + this.chipControlGroup.get('chipControl').patchValue(value, {emitEvent: false}); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(_fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + if (isDisabled) { + this.chipControlGroup.disable({emitEvent: false}); + } else { + this.chipControlGroup.enable({emitEvent: false}); + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/output-message-type-autocomplete.component.html b/ui-ngx/src/app/modules/home/components/rule-node/common/output-message-type-autocomplete.component.html new file mode 100644 index 0000000000..83e2547286 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/output-message-type-autocomplete.component.html @@ -0,0 +1,50 @@ + +
    + + {{'rule-node-config.output-message-type' | translate}} + + + {{msgType.name}} + + + + + {{'rule-node-config.message-type-value' | translate}} + + + + {{ 'rule-node-config.message-type-value-required' | translate }} + + + {{ 'rule-node-config.message-type-value-max-length' | translate }} + + +
    + diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/output-message-type-autocomplete.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/common/output-message-type-autocomplete.component.ts new file mode 100644 index 0000000000..b824240968 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/output-message-type-autocomplete.component.ts @@ -0,0 +1,172 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input } from '@angular/core'; +import { + ControlValueAccessor, + FormBuilder, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + Validator, + Validators +} from '@angular/forms'; +import { SubscriptSizing } from '@angular/material/form-field'; +import { coerceBoolean } from '@shared/public-api'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; + +interface MessageType { + name: string; + value: string; +} + +@Component({ + selector: 'tb-output-message-type-autocomplete', + templateUrl: './output-message-type-autocomplete.component.html', + styleUrls: [], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => OutputMessageTypeAutocompleteComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => OutputMessageTypeAutocompleteComponent), + multi: true + } + ] +}) + +export class OutputMessageTypeAutocompleteComponent implements ControlValueAccessor, Validator { + + @Input() + subscriptSizing: SubscriptSizing = 'fixed'; + + @Input() + @coerceBoolean() + disabled: boolean; + + @Input() + @coerceBoolean() + set required(value) { + if (this.requiredValue !== value) { + this.requiredValue = value; + this.updateValidators(); + } + } + + get required() { + return this.requiredValue; + } + + messageTypeFormGroup: FormGroup; + + messageTypes: MessageType[] = [ + { + name: 'Post attributes', + value: 'POST_ATTRIBUTES_REQUEST' + }, + { + name: 'Post telemetry', + value: 'POST_TELEMETRY_REQUEST' + }, + { + name: 'Custom', + value: '' + }, + ]; + + private modelValue: string | null; + private requiredValue: boolean; + private propagateChange: (value: any) => void = () => {}; + + constructor(private fb: FormBuilder) { + this.messageTypeFormGroup = this.fb.group({ + messageTypeAlias: [null, [Validators.required]], + messageType: [{value: null, disabled: true}, [Validators.maxLength(255)]] + }); + this.messageTypeFormGroup.get('messageTypeAlias').valueChanges + .pipe(takeUntilDestroyed()) + .subscribe(value => this.updateMessageTypeValue(value)); + this.messageTypeFormGroup.valueChanges + .pipe(takeUntilDestroyed()) + .subscribe(() => this.updateView()); + } + + registerOnTouched(_fn: any): void { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + writeValue(value: string | null): void { + this.modelValue = value; + let findMessage = this.messageTypes.find(msgType => msgType.value === value); + if (!findMessage) { + findMessage = this.messageTypes.find(msgType => msgType.value === ''); + } + this.messageTypeFormGroup.get('messageTypeAlias').patchValue(findMessage, {emitEvent: false}); + this.messageTypeFormGroup.get('messageType').patchValue(value, {emitEvent: false}); + } + + validate() { + if (!this.messageTypeFormGroup.valid) { + return { + messageTypeInvalid: true + }; + } + return null; + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (isDisabled) { + this.messageTypeFormGroup.disable({emitEvent: false}); + } else { + this.messageTypeFormGroup.enable({emitEvent: false}); + if (this.messageTypeFormGroup.get('messageTypeAlias').value?.name !== 'Custom') { + this.messageTypeFormGroup.get('messageType').disable({emitEvent: false}); + } + } + } + + private updateView() { + const value = this.messageTypeFormGroup.getRawValue().messageType; + if (this.modelValue !== value) { + this.modelValue = value; + this.propagateChange(this.modelValue); + } + } + + private updateValidators() { + this.messageTypeFormGroup.get('messageType').setValidators( + this.required ? [Validators.required, Validators.maxLength(255)] : [Validators.maxLength(255)] + ); + this.messageTypeFormGroup.get('messageType').updateValueAndValidity({emitEvent: false}); + } + + private updateMessageTypeValue(choseMessageType: MessageType) { + if (choseMessageType?.name !== 'Custom') { + this.messageTypeFormGroup.get('messageType').disable({emitEvent: false}); + } else { + this.messageTypeFormGroup.get('messageType').enable({emitEvent: false}); + } + this.messageTypeFormGroup.get('messageType').patchValue(choseMessageType.value ?? null); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/relations-query-config-old.component.html b/ui-ngx/src/app/modules/home/components/rule-node/common/relations-query-config-old.component.html new file mode 100644 index 0000000000..6fb7a86005 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/relations-query-config-old.component.html @@ -0,0 +1,45 @@ + +
    + + {{ 'alias.last-level-relation' | translate }} + +
    + + relation.direction + + + {{ directionTypeTranslations.get(type) | translate }} + + + + + rule-node-config.max-relation-level + + +
    +
    relation.relation-filters
    + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/relations-query-config-old.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/common/relations-query-config-old.component.ts new file mode 100644 index 0000000000..261b2aadc3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/relations-query-config-old.component.ts @@ -0,0 +1,100 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, DestroyRef, forwardRef, Input, OnInit } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { EntitySearchDirection, entitySearchDirectionTranslations, PageComponent } from '@shared/public-api'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { RelationsQuery } from '../rule-node-config.models'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; + +@Component({ + selector: 'tb-relations-query-config-old', + templateUrl: './relations-query-config-old.component.html', + styleUrls: [], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => RelationsQueryConfigOldComponent), + multi: true + } + ] +}) +export class RelationsQueryConfigOldComponent extends PageComponent implements ControlValueAccessor, OnInit { + + @Input() disabled: boolean; + + private requiredValue: boolean; + + get required(): boolean { + return this.requiredValue; + } + + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + directionTypes = Object.keys(EntitySearchDirection); + directionTypeTranslations = entitySearchDirectionTranslations; + + relationsQueryFormGroup: FormGroup; + + private propagateChange = null; + + constructor(private fb: FormBuilder, + private destroyRef: DestroyRef) { + super(); + } + + ngOnInit(): void { + this.relationsQueryFormGroup = this.fb.group({ + fetchLastLevelOnly: [false, []], + direction: [null, [Validators.required]], + maxLevel: [null, []], + filters: [null] + }); + this.relationsQueryFormGroup.valueChanges.pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe((query: RelationsQuery) => { + if (this.relationsQueryFormGroup.valid) { + this.propagateChange(query); + } else { + this.propagateChange(null); + } + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(_fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.relationsQueryFormGroup.disable({emitEvent: false}); + } else { + this.relationsQueryFormGroup.enable({emitEvent: false}); + } + } + + writeValue(query: RelationsQuery): void { + this.relationsQueryFormGroup.reset(query || {}, {emitEvent: false}); + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/relations-query-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/common/relations-query-config.component.html new file mode 100644 index 0000000000..01afac76b6 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/relations-query-config.component.html @@ -0,0 +1,61 @@ + +
    +
    rule-node-config.relations-query
    +
    +
    + + relation.direction + + + {{ directionTypeTranslations.get(type) | translate }} rule-node-config.relations-query-config-direction-suffix + + + + + rule-node-config.max-relation-level + + + {{ 'rule-node-config.max-relation-level-error' | translate }} + + + {{ 'rule-node-config.max-relation-level-invalid' | translate }} + + +
    +
    + + {{ 'alias.last-level-relation' | translate }} + +
    +
    +
    +
    relation.relation-filters
    + + +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/relations-query-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/common/relations-query-config.component.ts new file mode 100644 index 0000000000..5ed0eadbcc --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/relations-query-config.component.ts @@ -0,0 +1,99 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, DestroyRef, forwardRef, Input, OnInit } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { EntitySearchDirection, entitySearchDirectionTranslations, PageComponent } from '@shared/public-api'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { RelationsQuery } from '../rule-node-config.models'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; + +@Component({ + selector: 'tb-relations-query-config', + templateUrl: './relations-query-config.component.html', + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => RelationsQueryConfigComponent), + multi: true + } + ] +}) +export class RelationsQueryConfigComponent extends PageComponent implements ControlValueAccessor, OnInit { + + @Input() disabled: boolean; + + private requiredValue: boolean; + + get required(): boolean { + return this.requiredValue; + } + + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + directionTypes: Array = Object.values(EntitySearchDirection); + directionTypeTranslations = entitySearchDirectionTranslations; + + relationsQueryFormGroup: FormGroup; + + private propagateChange = null; + + constructor(private fb: FormBuilder, + private destroyRef: DestroyRef) { + super(); + } + + ngOnInit(): void { + this.relationsQueryFormGroup = this.fb.group({ + fetchLastLevelOnly: [false, []], + direction: [null, [Validators.required]], + maxLevel: [null, [Validators.min(1)]], + filters: [null] + }); + this.relationsQueryFormGroup.valueChanges.pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe((query: RelationsQuery) => { + if (this.relationsQueryFormGroup.valid) { + this.propagateChange(query); + } else { + this.propagateChange(null); + } + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(_fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.relationsQueryFormGroup.disable({emitEvent: false}); + } else { + this.relationsQueryFormGroup.enable({emitEvent: false}); + } + } + + writeValue(query: RelationsQuery): void { + this.relationsQueryFormGroup.reset(query || {}, {emitEvent: false}); + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/select-attributes.component.html b/ui-ngx/src/app/modules/home/components/rule-node/common/select-attributes.component.html new file mode 100644 index 0000000000..08de169272 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/select-attributes.component.html @@ -0,0 +1,59 @@ + +
    + + + + + + + + + + + + + +
    + + {{ 'rule-node-config.fetch-latest-telemetry-with-timestamp' | translate }} + +
    +
    + + + help + diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/select-attributes.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/common/select-attributes.component.ts new file mode 100644 index 0000000000..2bebd875ae --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/select-attributes.component.ts @@ -0,0 +1,133 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, DestroyRef, forwardRef, Input, OnInit } from '@angular/core'; +import { + ControlValueAccessor, + FormBuilder, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + ValidationErrors, + ValidatorFn, + Validators +} from '@angular/forms'; +import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes'; +import { TranslateService } from '@ngx-translate/core'; +import { isDefinedAndNotNull } from '@core/public-api'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; + +@Component({ + selector: 'tb-select-attributes', + templateUrl: './select-attributes.component.html', + styleUrls: [], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => SelectAttributesComponent), + multi: true + }, { + provide: NG_VALIDATORS, + useExisting: SelectAttributesComponent, + multi: true + }] +}) + +export class SelectAttributesComponent implements OnInit, ControlValueAccessor { + + private propagateChange = (v: any) => { }; + + public attributeControlGroup: FormGroup; + public separatorKeysCodes = [ENTER, COMMA, SEMICOLON]; + public onTouched = () => {}; + + @Input() popupHelpLink: string; + + constructor(public translate: TranslateService, + private fb: FormBuilder, + private destroyRef: DestroyRef) { + } + + ngOnInit(): void { + this.attributeControlGroup = this.fb.group({ + clientAttributeNames: [[], []], + sharedAttributeNames: [[], []], + serverAttributeNames: [[], []], + latestTsKeyNames: [[], []], + getLatestValueWithTs: [false, []] + }, { + validators: this.atLeastOne(Validators.required, ['clientAttributeNames', 'sharedAttributeNames', + 'serverAttributeNames', 'latestTsKeyNames']) + }); + + this.attributeControlGroup.valueChanges.pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe((value) => { + this.propagateChange(this.preparePropagateValue(value)); + }); + } + + private preparePropagateValue(propagateValue: {[key: string]: string[] | boolean | null}): {[key: string]: string[] | boolean } { + const formatValue = {}; + for (const key in propagateValue) { + if (key === 'getLatestValueWithTs') { + formatValue[key] = propagateValue[key]; + } else { + formatValue[key] = isDefinedAndNotNull(propagateValue[key]) ? propagateValue[key] : []; + } + } + + return formatValue; + }; + + validate() { + if (this.attributeControlGroup.valid) { + return null; + } else { + return {atLeastOneRequired: true}; + } + } + + private atLeastOne(validator: ValidatorFn, controls: string[] = null) { + return (group: FormGroup): ValidationErrors | null => { + if (!controls) { + controls = Object.keys(group.controls); + } + const hasAtLeastOne = group?.controls && controls.some(k => !validator(group.controls[k])); + + return hasAtLeastOne ? null : {atLeastOne: true}; + }; + } + + writeValue(value): void { + this.attributeControlGroup.setValue(value, {emitEvent: false}); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + this.onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + if (isDisabled) { + this.attributeControlGroup.disable({emitEvent: false}); + } else { + this.attributeControlGroup.enable({emitEvent: false}); + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/sv-map-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/common/sv-map-config.component.html new file mode 100644 index 0000000000..21ed014ec3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/sv-map-config.component.html @@ -0,0 +1,70 @@ + +
    +
    +
    {{ labelText }}
    +
    + rule-node-config.map-fields-required +
    +
    + {{ requiredText }} +
    +
    +
    +
    +
    +
    {{ selectText }}
    +
    {{ valText }}
    +
    +
    +
    +
    + + + + {{option.name}} + + + + + + +
    + +
    +
    +
    +
    +
    +
    + +
    + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/sv-map-config.component.scss b/ui-ngx/src/app/modules/home/components/rule-node/common/sv-map-config.component.scss new file mode 100644 index 0000000000..132a1f882c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/sv-map-config.component.scss @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + .field-space { + flex: 1 1 50%; + } + + .actions-header { + width: 40px + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/sv-map-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/common/sv-map-config.component.ts new file mode 100644 index 0000000000..0a836fc508 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/sv-map-config.component.ts @@ -0,0 +1,254 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, DestroyRef, forwardRef, Injector, Input, OnInit } from '@angular/core'; +import { + AbstractControl, + ControlValueAccessor, + FormArray, + FormBuilder, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + NgControl, + ValidationErrors, + Validator, + ValidatorFn, + Validators +} from '@angular/forms'; +import { coerceBoolean, PageComponent } from '@shared/public-api'; +import { isDefinedAndNotNull, isEqual } from '@core/public-api'; +import { TranslateService } from '@ngx-translate/core'; +import { OriginatorFieldsMappingValues, SvMapOption } from '../rule-node-config.models'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; + +@Component({ + selector: 'tb-sv-map-config', + templateUrl: './sv-map-config.component.html', + styleUrls: ['./sv-map-config.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => SvMapConfigComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => SvMapConfigComponent), + multi: true, + } + ] +}) +export class SvMapConfigComponent extends PageComponent implements ControlValueAccessor, OnInit, Validator { + + private propagateChange = null; + + svListFormGroup: FormGroup; + ngControl: NgControl; + + @Input() selectOptions: SvMapOption[]; + + @Input() + @coerceBoolean() + disabled = false; + + @Input() labelText: string; + + @Input() requiredText: string; + + @Input() targetKeyPrefix: string; + + @Input() selectText: string; + + @Input() selectRequiredText: string; + + @Input() valText: string; + + @Input() valRequiredText: string; + + @Input() hintText: string; + + @Input() popupHelpLink: string; + + @Input() + @coerceBoolean() + required = false; + + constructor(public translate: TranslateService, + private injector: Injector, + private fb: FormBuilder, + private destroyRef: DestroyRef) { + super(); + } + + ngOnInit(): void { + this.ngControl = this.injector.get(NgControl); + if (this.ngControl != null) { + this.ngControl.valueAccessor = this; + } + + this.svListFormGroup = this.fb.group({ + keyVals: this.fb.array([]) + }, {validators: [this.propagateNestedErrors, this.oneMapRequiredValidator]}); + + this.svListFormGroup.valueChanges + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(() => { + this.updateModel(); + }); + } + + keyValsFormArray(): FormArray { + return this.svListFormGroup.get('keyVals') as FormArray; + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(_fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.svListFormGroup.disable({emitEvent: false}); + } else { + this.svListFormGroup.enable({emitEvent: false}); + } + } + + private oneMapRequiredValidator: ValidatorFn = (control: FormGroup): ValidationErrors | null => control.get('keyVals').value.length; + + private propagateNestedErrors: ValidatorFn = (controls: FormArray | FormGroup | AbstractControl): ValidationErrors | null => { + if (this.svListFormGroup && this.svListFormGroup.get('keyVals') && this.svListFormGroup.get('keyVals')?.status === 'VALID') { + return null; + } + const errors = {}; + if (this.svListFormGroup) {this.svListFormGroup.setErrors(null);} + if (controls instanceof FormArray || controls instanceof FormGroup) { + if (controls.errors) { + for (const errorKey of Object.keys(controls.errors)) { + errors[errorKey] = true; + } + } + for (const control of Object.keys(controls.controls)) { + const innerErrors = this.propagateNestedErrors(controls.controls[control]); + if (innerErrors && Object.keys(innerErrors).length) { + for (const errorKey of Object.keys(innerErrors)) { + errors[errorKey] = true; + } + } + } + return errors; + } else { + if (controls.errors) { + for (const errorKey of Object.keys(controls.errors)) { + errors[errorKey] = true; + } + } + } + return !isEqual(errors, {}) ? errors : null; + }; + + writeValue(keyValMap: { [key: string]: string }): void { + const keyValuesData = Object.keys(keyValMap).map(key => ({key, value: keyValMap[key]})); + if (this.keyValsFormArray().length === keyValuesData.length) { + this.keyValsFormArray().patchValue(keyValuesData, {emitEvent: false}) + } else { + const keyValsControls: Array = []; + keyValuesData.forEach(data => { + keyValsControls.push(this.fb.group({ + key: [data.key, [Validators.required, ]], + value: [data.value, [Validators.required, Validators.pattern(/(?:.|\s)*\S(&:.|\s)*/)]] + })); + }); + this.svListFormGroup.setControl('keyVals', this.fb.array(keyValsControls, this.propagateNestedErrors), {emitEvent: false}); + for (const formGroup of this.keyValsFormArray().controls) { + this.keyChangeSubscribe(formGroup as FormGroup); + } + } + } + + public filterSelectOptions(keyValControl?: AbstractControl) { + const deleteFieldsArray = []; + for (const fieldMap of this.svListFormGroup.get('keyVals').value) { + const findDeleteField = this.selectOptions.find((field) => field.value === fieldMap.key); + if (findDeleteField) { + deleteFieldsArray.push(findDeleteField); + } + } + + const filterSelectOptions = []; + for (const selectOption of this.selectOptions) { + if (!isDefinedAndNotNull(deleteFieldsArray.find((deleteField) => deleteField.value === selectOption.value)) || + selectOption.value === keyValControl?.get('key').value) { + filterSelectOptions.push(selectOption); + } + } + + return filterSelectOptions; + } + + public removeKeyVal(index: number) { + this.keyValsFormArray().removeAt(index); + } + + public addKeyVal() { + this.keyValsFormArray().push(this.fb.group({ + key: ['', [Validators.required]], + value: ['', [Validators.required, Validators.pattern(/(?:.|\s)*\S(&:.|\s)*/)]] + })); + this.keyChangeSubscribe(this.keyValsFormArray().at(this.keyValsFormArray().length - 1) as FormGroup); + } + + private keyChangeSubscribe(formGroup: FormGroup) { + formGroup.get('key').valueChanges.pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe((value) => { + const mappedValue = OriginatorFieldsMappingValues.get(value); + formGroup.get('value').patchValue(this.targetKeyPrefix + mappedValue[0].toUpperCase() + mappedValue.slice(1)); + }); + } + + public validate() { + const svList: { key: string; value: string }[] = this.svListFormGroup.get('keyVals').value; + if (!svList.length && this.required) { + return { + svMapRequired: true + }; + } + if (!this.svListFormGroup.valid) { + return { + svFieldsRequired: true + }; + } + return null; + } + + private updateModel() { + const svList: { key: string; value: string }[] = this.svListFormGroup.get('keyVals').value; + if (this.required && !svList.length || !this.svListFormGroup.valid) { + this.propagateChange(null); + } else { + const keyValMap: { [key: string]: string } = {}; + svList.forEach((entry) => { + keyValMap[entry.key] = entry.value; + }); + this.propagateChange(keyValMap); + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/time-unit-input.component.html b/ui-ngx/src/app/modules/home/components/rule-node/common/time-unit-input.component.html new file mode 100644 index 0000000000..8fe112f62c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/time-unit-input.component.html @@ -0,0 +1,44 @@ + +
    + + {{ labelText }} + +
    + +
    + + + {{ requiredText }} + + + {{ minErrorText }} + + + {{ maxErrorText }} + +
    + + rule-node-config.units + + @for (timeUnit of timeUnits; track timeUnit) { + {{ timeUnitTranslations.get(timeUnit) | translate }} + } + + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/time-unit-input.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/common/time-unit-input.component.ts new file mode 100644 index 0000000000..11358ae6eb --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/time-unit-input.component.ts @@ -0,0 +1,199 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, DestroyRef, forwardRef, Input, OnInit } from '@angular/core'; +import { + AbstractControl, + ControlValueAccessor, + FormBuilder, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + ValidationErrors, + Validator, + Validators +} from '@angular/forms'; +import { TimeUnit, timeUnitTranslations } from '../rule-node-config.models'; +import { isDefinedAndNotNull, isNumeric } from '@core/utils'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { coerceBoolean, coerceNumber } from '@shared/decorators/coercion'; +import { DAY, HOUR, MINUTE, SECOND } from '@shared/models/time/time.models'; +import { SubscriptSizing } from '@angular/material/form-field'; + +interface TimeUnitInputModel { + time: number; + timeUnit: TimeUnit +} + +@Component({ + selector: 'tb-time-unit-input', + templateUrl: './time-unit-input.component.html', + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TimeUnitInputComponent), + multi: true + },{ + provide: NG_VALIDATORS, + useExisting: forwardRef(() => TimeUnitInputComponent), + multi: true + }] +}) +export class TimeUnitInputComponent implements ControlValueAccessor, Validator, OnInit { + + @Input() + labelText: string; + + @Input() + @coerceBoolean() + required: boolean; + + @Input() + requiredText: string; + + @Input() + @coerceNumber() + minTime = 0; + + @Input() + minErrorText: string; + + @Input() + @coerceNumber() + maxTime: number; + + @Input() + maxErrorText: string; + + @Input() + subscriptSizing: SubscriptSizing = 'fixed'; + + timeUnits = Object.values(TimeUnit).filter(item => item !== TimeUnit.MILLISECONDS) as TimeUnit[]; + + timeUnitTranslations = timeUnitTranslations; + + timeInputForm = this.fb.group({ + time: [0], + timeUnit: [TimeUnit.SECONDS] + }); + + private timeIntervalsInSec = new Map([ + [TimeUnit.DAYS, DAY/SECOND], + [TimeUnit.HOURS, HOUR/SECOND], + [TimeUnit.MINUTES, MINUTE/SECOND], + [TimeUnit.SECONDS, SECOND/SECOND], + ]); + + private modelValue: number; + + private propagateChange: (value: any) => void = () => {}; + + constructor(private fb: FormBuilder, + private destroyRef: DestroyRef) { + } + + ngOnInit() { + if(this.required || this.maxTime) { + const timeControl = this.timeInputForm.get('time'); + const validators = [Validators.pattern(/^\d*$/)]; + if (this.required) { + validators.push(Validators.required); + } + if (this.maxTime) { + validators.push((control: AbstractControl) => + Validators.max(Math.floor(this.maxTime / this.timeIntervalsInSec.get(this.timeInputForm.get('timeUnit').value)))(control) + ); + } + if (isDefinedAndNotNull(this.minTime)) { + validators.push(Validators.min(this.minTime)); + } + + timeControl.setValidators(validators); + timeControl.updateValueAndValidity({ emitEvent: false }); + } + + this.timeInputForm.get('timeUnit').valueChanges.pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe(() => { + this.timeInputForm.get('time').updateValueAndValidity({onlySelf: true}); + this.timeInputForm.get('time').markAsTouched({onlySelf: true}); + }); + + this.timeInputForm.valueChanges.pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe(value => { + this.updatedModel(value); + }); + } + + registerOnChange(fn: any) { + this.propagateChange = fn; + } + + registerOnTouched(_fn: any) { + } + + setDisabledState(isDisabled: boolean) { + if (isDisabled) { + this.timeInputForm.disable({emitEvent: false}); + } else { + this.timeInputForm.enable({emitEvent: false}); + if(this.timeInputForm.invalid) { + setTimeout(() => this.updatedModel(this.timeInputForm.value, true)) + } + } + } + + writeValue(sec: number) { + if (sec !== this.modelValue) { + if (isDefinedAndNotNull(sec) && isNumeric(sec) && Number(sec) !== 0) { + this.timeInputForm.patchValue(this.parseTime(sec), {emitEvent: false}); + this.modelValue = sec; + } else { + this.timeInputForm.patchValue({ + time: 0, + timeUnit: TimeUnit.SECONDS + }, {emitEvent: false}); + this.modelValue = 0; + } + } + } + + validate(): ValidationErrors | null { + return this.timeInputForm.valid ? null : { + timeInput: false + }; + } + + private updatedModel(value: Partial, forceUpdated = false) { + const time = value.time * this.timeIntervalsInSec.get(value.timeUnit); + if (this.modelValue !== time || forceUpdated) { + this.modelValue = time; + this.propagateChange(time); + } + } + + private parseTime(value: number): TimeUnitInputModel { + for (const [timeUnit, timeValue] of this.timeIntervalsInSec) { + const calc = value / timeValue; + if (Number.isInteger(calc)) { + return { + time: calc, + timeUnit: timeUnit + } + } + } + } + +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/empty-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/empty-config.component.ts new file mode 100644 index 0000000000..48f350157f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/empty-config.component.ts @@ -0,0 +1,42 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; + +@Component({ + selector: 'tb-node-empty-config', + template: '
    ', + styleUrls: [] +}) +export class EmptyConfigComponent extends RuleNodeConfigurationComponent { + + emptyConfigForm: UntypedFormGroup; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.emptyConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.emptyConfigForm = this.fb.group({}); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/enrichment/calculate-delta-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/calculate-delta-config.component.html new file mode 100644 index 0000000000..6d9924f4f8 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/calculate-delta-config.component.html @@ -0,0 +1,92 @@ + +
    +
    + + {{ 'rule-node-config.input-value-key' | translate }} + + + {{ 'rule-node-config.input-value-key-required' | translate }} + + + + {{ 'rule-node-config.output-value-key' | translate }} + + + {{ 'rule-node-config.output-value-key-required' | translate }} + + +
    + + {{ 'rule-node-config.number-of-digits-after-floating-point' | translate }} + + + {{ 'rule-node-config.number-of-digits-after-floating-point-range' | translate }} + + + {{ 'rule-node-config.number-of-digits-after-floating-point-range' | translate }} + + +
    +
    + + {{ 'rule-node-config.failure-if-delta-negative' | translate }} + +
    +
    + + {{ 'rule-node-config.use-caching' | translate }} + +
    +
    +
    + + {{ 'rule-node-config.add-time-difference-between-readings' | translate: + { inputValueKey: calculateDeltaConfigForm.get('inputValueKey').valid ? + calculateDeltaConfigForm.get('inputValueKey').value : 'rule-node-config.input-value-key' | translate } }} + +
    + + {{ 'rule-node-config.period-value-key' | translate }} + + + {{ 'rule-node-config.period-value-key-required' | translate }} + + +
    +
    + + {{ 'rule-node-config.exclude-zero-deltas' | translate }} + +
    +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/enrichment/calculate-delta-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/calculate-delta-config.component.ts new file mode 100644 index 0000000000..1721b5dfb0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/calculate-delta-config.component.ts @@ -0,0 +1,87 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes'; +import { TranslateService } from '@ngx-translate/core'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; +import { deepTrim, isDefinedAndNotNull } from '@app/core/utils'; + +@Component({ + selector: 'tb-enrichment-node-calculate-delta-config', + templateUrl: './calculate-delta-config.component.html' +}) +export class CalculateDeltaConfigComponent extends RuleNodeConfigurationComponent { + + calculateDeltaConfigForm: FormGroup; + + separatorKeysCodes = [ENTER, COMMA, SEMICOLON]; + + constructor(public translate: TranslateService, + private fb: FormBuilder) { + super(); + } + + protected configForm(): FormGroup { + return this.calculateDeltaConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.calculateDeltaConfigForm = this.fb.group({ + inputValueKey: [configuration.inputValueKey, [Validators.required, Validators.pattern(/(?:.|\s)*\S(&:.|\s)*/)]], + outputValueKey: [configuration.outputValueKey, [Validators.required, Validators.pattern(/(?:.|\s)*\S(&:.|\s)*/)]], + useCache: [configuration.useCache, []], + addPeriodBetweenMsgs: [configuration.addPeriodBetweenMsgs, []], + periodValueKey: [configuration.periodValueKey, []], + round: [configuration.round, [Validators.min(0), Validators.max(15)]], + tellFailureIfDeltaIsNegative: [configuration.tellFailureIfDeltaIsNegative, []], + excludeZeroDeltas: [configuration.excludeZeroDeltas, []] + }); + } + + protected prepareInputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + return { + inputValueKey: isDefinedAndNotNull(configuration?.inputValueKey) ? configuration.inputValueKey : null, + outputValueKey: isDefinedAndNotNull(configuration?.outputValueKey) ? configuration.outputValueKey : null, + useCache: isDefinedAndNotNull(configuration?.useCache) ? configuration.useCache : true, + addPeriodBetweenMsgs: isDefinedAndNotNull(configuration?.addPeriodBetweenMsgs) ? configuration.addPeriodBetweenMsgs : false, + periodValueKey: isDefinedAndNotNull(configuration?.periodValueKey) ? configuration.periodValueKey : null, + round: isDefinedAndNotNull(configuration?.round) ? configuration.round : null, + tellFailureIfDeltaIsNegative: isDefinedAndNotNull(configuration?.tellFailureIfDeltaIsNegative) ? + configuration.tellFailureIfDeltaIsNegative : true, + excludeZeroDeltas: isDefinedAndNotNull(configuration?.excludeZeroDeltas) ? configuration.excludeZeroDeltas : false + }; + } + + protected prepareOutputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + return deepTrim(configuration); + } + + protected updateValidators(emitEvent: boolean) { + const addPeriodBetweenMsgs: boolean = this.calculateDeltaConfigForm.get('addPeriodBetweenMsgs').value; + if (addPeriodBetweenMsgs) { + this.calculateDeltaConfigForm.get('periodValueKey').setValidators([Validators.required]); + } else { + this.calculateDeltaConfigForm.get('periodValueKey').setValidators([]); + } + this.calculateDeltaConfigForm.get('periodValueKey').updateValueAndValidity({emitEvent}); + } + + protected validatorTriggers(): string[] { + return ['addPeriodBetweenMsgs']; + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/enrichment/customer-attributes-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/customer-attributes-config.component.html new file mode 100644 index 0000000000..20377be84b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/customer-attributes-config.component.html @@ -0,0 +1,46 @@ + +
    +
    rule-node-config.mapping-of-customers
    +
    +
    + + + {{ data.name }} + + +
    +
    + + + + +
    diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/MsgConstructor.java b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/customer-attributes-config.component.scss similarity index 86% rename from application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/MsgConstructor.java rename to ui-ngx/src/app/modules/home/components/rule-node/enrichment/customer-attributes-config.component.scss index c751c6259c..aea1da60f3 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/MsgConstructor.java +++ b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/customer-attributes-config.component.scss @@ -13,8 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.edge.rpc.constructor; +:host { + .fetch-to-data-toggle { + max-width: 420px; + width: 100%; + } +} -public interface MsgConstructor { -} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/enrichment/customer-attributes-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/customer-attributes-config.component.ts new file mode 100644 index 0000000000..3eabe0ab4f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/customer-attributes-config.component.ts @@ -0,0 +1,100 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { deepTrim, isDefinedAndNotNull } from '@core/public-api'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@app/shared/models/rule-node.models'; +import { DataToFetch, dataToFetchTranslations, FetchTo } from '@home/components/rule-node/rule-node-config.models'; + +@Component({ + selector: 'tb-enrichment-node-customer-attributes-config', + templateUrl: './customer-attributes-config.component.html', + styleUrls: ['./customer-attributes-config.component.scss'] +}) +export class CustomerAttributesConfigComponent extends RuleNodeConfigurationComponent { + + customerAttributesConfigForm: FormGroup; + + public fetchToData = []; + + constructor(private fb: FormBuilder, + private translate: TranslateService) { + super(); + for (const key of dataToFetchTranslations.keys()) { + if (key !== DataToFetch.FIELDS) { + this.fetchToData.push({ + value: key, + name: this.translate.instant(dataToFetchTranslations.get(key as DataToFetch)) + }); + } + } + } + + protected configForm(): FormGroup { + return this.customerAttributesConfigForm; + } + + protected prepareOutputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + const filteDataMapping = {}; + for (const key of Object.keys(configuration.dataMapping)) { + filteDataMapping[key.trim()] = configuration.dataMapping[key]; + } + configuration.dataMapping = filteDataMapping; + return deepTrim(configuration); + } + + protected prepareInputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + let dataToFetch: DataToFetch; + if (isDefinedAndNotNull(configuration?.telemetry)) { + dataToFetch = configuration.telemetry ? DataToFetch.LATEST_TELEMETRY : DataToFetch.ATTRIBUTES; + } else { + dataToFetch = isDefinedAndNotNull(configuration?.dataToFetch) ? configuration.dataToFetch : DataToFetch.ATTRIBUTES; + } + + let dataMapping; + if (isDefinedAndNotNull(configuration?.attrMapping)) { + dataMapping = configuration.attrMapping; + } else { + dataMapping = isDefinedAndNotNull(configuration?.dataMapping) ? configuration.dataMapping : null; + } + + return { + dataToFetch, + dataMapping, + fetchTo: isDefinedAndNotNull(configuration?.fetchTo) ? configuration.fetchTo : FetchTo.METADATA + }; + } + + public selectTranslation(latestTelemetryTranslation: string, attributesTranslation: string) { + if (this.customerAttributesConfigForm.get('dataToFetch').value === DataToFetch.LATEST_TELEMETRY) { + return latestTelemetryTranslation; + } else { + return attributesTranslation; + } + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.customerAttributesConfigForm = this.fb.group({ + dataToFetch: [configuration.dataToFetch, []], + dataMapping: [configuration.dataMapping, [Validators.required]], + fetchTo: [configuration.fetchTo] + }); + } + + protected readonly DataToFetch = DataToFetch; +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/enrichment/device-attributes-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/device-attributes-config.component.html new file mode 100644 index 0000000000..f2c33cf0b5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/device-attributes-config.component.html @@ -0,0 +1,46 @@ + +
    +
    +
    rule-node-config.device-relations-query
    + + +
    +
    +
    +
    rule-node-config.related-device-attributes
    +
    + rule-node-config.at-least-one-field-required +
    +
    + + +
    +
    + + {{ 'rule-node-config.tell-failure' | translate }} + +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/enrichment/device-attributes-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/device-attributes-config.component.ts new file mode 100644 index 0000000000..876e672b13 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/device-attributes-config.component.ts @@ -0,0 +1,77 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { isDefinedAndNotNull, isObject } from '@core/public-api'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@app/shared/models/rule-node.models'; +import { FetchTo } from '@home/components/rule-node/rule-node-config.models'; + +@Component({ + selector: 'tb-enrichment-node-device-attributes-config', + templateUrl: './device-attributes-config.component.html', + styleUrls: [] +}) +export class DeviceAttributesConfigComponent extends RuleNodeConfigurationComponent { + + deviceAttributesConfigForm: FormGroup; + + constructor(public translate: TranslateService, + private fb: FormBuilder) { + super(); + } + + protected configForm(): FormGroup { + return this.deviceAttributesConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.deviceAttributesConfigForm = this.fb.group({ + deviceRelationsQuery: [configuration.deviceRelationsQuery, [Validators.required]], + tellFailureIfAbsent: [configuration.tellFailureIfAbsent, []], + fetchTo: [configuration.fetchTo, []], + attributesControl: [configuration.attributesControl, []] + }); + } + + protected prepareInputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + if (isObject(configuration)) { + configuration.attributesControl = { + clientAttributeNames: isDefinedAndNotNull(configuration?.clientAttributeNames) ? configuration.clientAttributeNames : [], + latestTsKeyNames: isDefinedAndNotNull(configuration?.latestTsKeyNames) ? configuration.latestTsKeyNames : [], + serverAttributeNames: isDefinedAndNotNull(configuration?.serverAttributeNames) ? configuration.serverAttributeNames : [], + sharedAttributeNames: isDefinedAndNotNull(configuration?.sharedAttributeNames) ? configuration.sharedAttributeNames : [], + getLatestValueWithTs: isDefinedAndNotNull(configuration?.getLatestValueWithTs) ? configuration.getLatestValueWithTs : false, + }; + } + + return { + deviceRelationsQuery: isDefinedAndNotNull(configuration?.deviceRelationsQuery) ? configuration.deviceRelationsQuery : null, + tellFailureIfAbsent: isDefinedAndNotNull(configuration?.tellFailureIfAbsent) ? configuration.tellFailureIfAbsent : true, + fetchTo: isDefinedAndNotNull(configuration?.fetchTo) ? configuration.fetchTo : FetchTo.METADATA, + attributesControl: configuration ? configuration.attributesControl : null + }; + } + + protected prepareOutputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + for (const key of Object.keys(configuration.attributesControl)) { + configuration[key] = configuration.attributesControl[key]; + } + delete configuration.attributesControl; + return configuration; + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/enrichment/enrichment-rule-node-core.module.ts b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/enrichment-rule-node-core.module.ts new file mode 100644 index 0000000000..a20f387e34 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/enrichment-rule-node-core.module.ts @@ -0,0 +1,64 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@shared/public-api'; +import { CustomerAttributesConfigComponent } from './customer-attributes-config.component'; +import { CommonRuleNodeConfigModule } from '../common/common-rule-node-config.module'; +import { EntityDetailsConfigComponent } from './entity-details-config.component'; +import { DeviceAttributesConfigComponent } from './device-attributes-config.component'; +import { OriginatorAttributesConfigComponent } from './originator-attributes-config.component'; +import { OriginatorFieldsConfigComponent } from './originator-fields-config.component'; +import { GetTelemetryFromDatabaseConfigComponent } from './get-telemetry-from-database-config.component'; +import { RelatedAttributesConfigComponent } from './related-attributes-config.component'; +import { TenantAttributesConfigComponent } from './tenant-attributes-config.component'; +import { CalculateDeltaConfigComponent } from './calculate-delta-config.component'; +import { FetchDeviceCredentialsConfigComponent } from './fetch-device-credentials-config.component'; + +@NgModule({ + declarations: [ + CustomerAttributesConfigComponent, + EntityDetailsConfigComponent, + DeviceAttributesConfigComponent, + OriginatorAttributesConfigComponent, + OriginatorFieldsConfigComponent, + GetTelemetryFromDatabaseConfigComponent, + RelatedAttributesConfigComponent, + TenantAttributesConfigComponent, + CalculateDeltaConfigComponent, + FetchDeviceCredentialsConfigComponent + ], + imports: [ + CommonModule, + SharedModule, + CommonRuleNodeConfigModule + ], + exports: [ + CustomerAttributesConfigComponent, + EntityDetailsConfigComponent, + DeviceAttributesConfigComponent, + OriginatorAttributesConfigComponent, + OriginatorFieldsConfigComponent, + GetTelemetryFromDatabaseConfigComponent, + RelatedAttributesConfigComponent, + TenantAttributesConfigComponent, + CalculateDeltaConfigComponent, + FetchDeviceCredentialsConfigComponent + ] +}) +export class EnrichmentRuleNodeCoreModule { +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/enrichment/entity-details-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/entity-details-config.component.html new file mode 100644 index 0000000000..07fc97a716 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/entity-details-config.component.html @@ -0,0 +1,35 @@ + +
    + + + help + + + + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/enrichment/entity-details-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/entity-details-config.component.ts new file mode 100644 index 0000000000..808eaf35b2 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/entity-details-config.component.ts @@ -0,0 +1,87 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, OnInit } from '@angular/core'; +import { isDefinedAndNotNull } from '@core/public-api'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@app/shared/models/rule-node.models'; +import { + EntityDetailsField, + entityDetailsTranslations, + FetchTo +} from '@home/components/rule-node/rule-node-config.models'; + +@Component({ + selector: 'tb-enrichment-node-entity-details-config', + templateUrl: './entity-details-config.component.html', + styleUrls: [] +}) + +export class EntityDetailsConfigComponent extends RuleNodeConfigurationComponent implements OnInit { + + entityDetailsConfigForm: FormGroup; + + public predefinedValues = []; + + constructor(public translate: TranslateService, + private fb: FormBuilder) { + super(); + for (const field of Object.keys(EntityDetailsField)) { + this.predefinedValues.push({ + value: EntityDetailsField[field], + name: this.translate.instant(entityDetailsTranslations.get(EntityDetailsField[field])) + }); + } + } + + ngOnInit() { + super.ngOnInit(); + } + + protected configForm(): FormGroup { + return this.entityDetailsConfigForm; + } + + protected prepareInputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + let fetchTo: FetchTo; + if (isDefinedAndNotNull(configuration?.addToMetadata)) { + if (configuration.addToMetadata) { + fetchTo = FetchTo.METADATA; + } else { + fetchTo = FetchTo.DATA; + } + } else { + if (configuration?.fetchTo) { + fetchTo = configuration.fetchTo; + } else { + fetchTo = FetchTo.DATA; + } + } + + return { + detailsList: isDefinedAndNotNull(configuration?.detailsList) ? configuration.detailsList : null, + fetchTo + }; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.entityDetailsConfigForm = this.fb.group({ + detailsList: [configuration.detailsList, [Validators.required]], + fetchTo: [configuration.fetchTo, []] + }); + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/enrichment/fetch-device-credentials-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/fetch-device-credentials-config.component.html new file mode 100644 index 0000000000..aa210dfcc1 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/fetch-device-credentials-config.component.html @@ -0,0 +1,23 @@ + +
    + + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/enrichment/fetch-device-credentials-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/fetch-device-credentials-config.component.ts new file mode 100644 index 0000000000..1b16674566 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/fetch-device-credentials-config.component.ts @@ -0,0 +1,51 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { isDefinedAndNotNull } from '@core/public-api'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; +import { FetchTo } from '@home/components/rule-node/rule-node-config.models'; + +@Component({ + selector: 'tb-enrichment-node-fetch-device-credentials-config', + templateUrl: './fetch-device-credentials-config.component.html' +}) + +export class FetchDeviceCredentialsConfigComponent extends RuleNodeConfigurationComponent { + + fetchDeviceCredentialsConfigForm: FormGroup; + + constructor(private fb: FormBuilder) { + super(); + } + + protected configForm(): FormGroup { + return this.fetchDeviceCredentialsConfigForm; + } + + protected prepareInputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + return { + fetchTo: isDefinedAndNotNull(configuration?.fetchTo) ? configuration.fetchTo : FetchTo.METADATA + }; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.fetchDeviceCredentialsConfigForm = this.fb.group({ + fetchTo: [configuration.fetchTo, []] + }); + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/enrichment/get-telemetry-from-database-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/get-telemetry-from-database-config.component.html new file mode 100644 index 0000000000..6dafe65457 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/get-telemetry-from-database-config.component.html @@ -0,0 +1,182 @@ + +
    + +
    + help + +
    +
    +
    rule-node-config.fetch-interval
    +
    + + {{ 'rule-node-config.use-metadata-dynamic-interval' | translate }} + +
    +
    +
    + + {{ 'rule-node-config.interval-start' | translate }} + + + {{ 'rule-node-config.start-interval-value-required' | translate }} + + + {{ 'rule-node-config.time-value-range' | translate }} + + + {{ 'rule-node-config.time-value-range' | translate }} + + + + {{ 'rule-node-config.time-unit' | translate }} + + + {{ timeUnitsTranslationMap.get(timeUnit) | translate }} + + + +
    +
    + + {{ 'rule-node-config.interval-end' | translate }} + + + {{ 'rule-node-config.end-interval-value-required' | translate }} + + + {{ 'rule-node-config.time-value-range' | translate }} + + + {{ 'rule-node-config.time-value-range' | translate }} + + + + {{ 'rule-node-config.time-unit' | translate }} + + + {{ timeUnitsTranslationMap.get(timeUnit) | translate }} + + + +
    +
    + error_outline +
    + + {{ 'rule-node-config.fetch-timeseries-from-to' | translate: + { + startInterval: getTelemetryFromDatabaseConfigForm.get('interval.startInterval').value, + endInterval: getTelemetryFromDatabaseConfigForm.get('interval.endInterval').value, + startIntervalTimeUnit: getTelemetryFromDatabaseConfigForm.get('interval.startIntervalTimeUnit').value.toLowerCase(), + endIntervalTimeUnit: getTelemetryFromDatabaseConfigForm.get('interval.endIntervalTimeUnit').value.toLowerCase() + } }} + + + {{ "rule-node-config.fetch-timeseries-from-to-invalid" | translate }} + +
    +
    +
    + +
    + + {{ 'rule-node-config.start-interval' | translate }} + + + {{ 'rule-node-config.start-interval-required' | translate }} + + + + {{ 'rule-node-config.end-interval' | translate }} + + + {{ 'rule-node-config.end-interval-required' | translate }} + + + + +
    +
    +
    +
    +
    rule-node-config.fetch-strategy
    +
    +
    + + + {{ data.name }} + + +
    +
    + {{ deduplicationStrategiesHintTranslations.get(getTelemetryFromDatabaseConfigForm.get('fetchMode').value) | translate }} +
    +
    +
    + + {{ 'aggregation.function' | translate }} + + + {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }} + + + +
    + + {{ "rule-node-config.order-by-timestamp" | translate }} + + + {{ samplingOrdersTranslate.get(order) | translate }} + + + + + {{ "rule-node-config.limit" | translate }} + + {{ "rule-node-config.limit-hint" | translate }} + + {{ 'rule-node-config.limit-required' | translate }} + + + {{ 'rule-node-config.limit-range' | translate }} + + + {{ 'rule-node-config.limit-range' | translate }} + + +
    +
    +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/enrichment/get-telemetry-from-database-config.component.scss b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/get-telemetry-from-database-config.component.scss new file mode 100644 index 0000000000..f1fe8725e3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/get-telemetry-from-database-config.component.scss @@ -0,0 +1,66 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + + .see-example { + display: inline-block; + } + + .description-block { + display: flex; + align-items: center; + border-radius: 6px; + border: 1px solid #EAEAEA; + + .description-icon { + font-size: 24px; + height: 24px; + min-height: 24px; + width: 24px; + min-width: 24px; + line-height: 24px; + color: #D9D9D9; + margin: 4px; + } + + .description-text { + font-size: 12px; + line-height: 16px; + letter-spacing: 0.25px; + margin: 6px; + } + + &.error { + color: var(--mdc-theme-error, #f44336); + + .description-icon { + color: var(--mdc-theme-error, #f44336); + } + } + } + .item-center { + align-items: center; + + .fetch-mod-toggle { + width: 100%; + } + } + + .hint-container { + width: 100%; + } +} + diff --git a/ui-ngx/src/app/modules/home/components/rule-node/enrichment/get-telemetry-from-database-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/get-telemetry-from-database-config.component.ts new file mode 100644 index 0000000000..13aeae87ed --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/get-telemetry-from-database-config.component.ts @@ -0,0 +1,205 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { deepTrim, isDefinedAndNotNull, isObject } from '@core/public-api'; +import { AbstractControl, FormBuilder, FormGroup, ValidationErrors, Validators } from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@app/shared/models/rule-node.models'; +import { aggregationTranslations, AggregationType } from '@app/shared/models/time/time.models'; +import { + deduplicationStrategiesHintTranslations, + deduplicationStrategiesTranslations, + FetchMode, + SamplingOrder, + samplingOrderTranslations, + TimeUnit, + timeUnitTranslations +} from '../rule-node-config.models'; + +@Component({ + selector: 'tb-enrichment-node-get-telemetry-from-database', + templateUrl: './get-telemetry-from-database-config.component.html', + styleUrls: ['./get-telemetry-from-database-config.component.scss'] +}) +export class GetTelemetryFromDatabaseConfigComponent extends RuleNodeConfigurationComponent { + + getTelemetryFromDatabaseConfigForm: FormGroup; + + aggregationTypes = AggregationType; + aggregations: Array = Object.values(AggregationType); + aggregationTypesTranslations = aggregationTranslations; + + fetchMode = FetchMode; + + samplingOrders: Array = Object.values(SamplingOrder); + samplingOrdersTranslate = samplingOrderTranslations; + + timeUnits: Array = Object.values(TimeUnit); + timeUnitsTranslationMap = timeUnitTranslations; + + public deduplicationStrategiesHintTranslations = deduplicationStrategiesHintTranslations; + + headerOptions = []; + + + timeUnitMap = { + [TimeUnit.MILLISECONDS]: 1, + [TimeUnit.SECONDS]: 1000, + [TimeUnit.MINUTES]: 60000, + [TimeUnit.HOURS]: 3600000, + [TimeUnit.DAYS]: 86400000, + }; + + constructor(public translate: TranslateService, + private fb: FormBuilder) { + super(); + for (const key of deduplicationStrategiesTranslations.keys()) { + this.headerOptions.push({ + value: key, + name: this.translate.instant(deduplicationStrategiesTranslations.get(key)) + }); + } + } + + protected configForm(): FormGroup { + return this.getTelemetryFromDatabaseConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.getTelemetryFromDatabaseConfigForm = this.fb.group({ + latestTsKeyNames: [configuration.latestTsKeyNames, [Validators.required]], + aggregation: [configuration.aggregation, [Validators.required]], + fetchMode: [configuration.fetchMode, [Validators.required]], + orderBy: [configuration.orderBy, []], + limit: [configuration.limit, []], + useMetadataIntervalPatterns: [configuration.useMetadataIntervalPatterns, []], + interval: this.fb.group({ + startInterval: [configuration.interval.startInterval, []], + startIntervalTimeUnit: [configuration.interval.startIntervalTimeUnit, []], + endInterval: [configuration.interval.endInterval, []], + endIntervalTimeUnit: [configuration.interval.endIntervalTimeUnit, []], + }), + startIntervalPattern: [configuration.startIntervalPattern, []], + endIntervalPattern: [configuration.endIntervalPattern, []], + }); + } + + + private intervalValidator = () => (control: AbstractControl): ValidationErrors | null => { + if (control.get('startInterval').value * this.timeUnitMap[control.get('startIntervalTimeUnit').value] <= + control.get('endInterval').value * this.timeUnitMap[control.get('endIntervalTimeUnit').value]) { + return {intervalError: true}; + } else { + return null; + } + }; + + + protected validatorTriggers(): string[] { + return ['fetchMode', 'useMetadataIntervalPatterns']; + } + + protected prepareOutputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + configuration.startInterval = configuration.interval.startInterval; + configuration.startIntervalTimeUnit = configuration.interval.startIntervalTimeUnit; + configuration.endInterval = configuration.interval.endInterval; + configuration.endIntervalTimeUnit = configuration.interval.endIntervalTimeUnit; + delete configuration.interval; + return deepTrim(configuration); + } + + protected prepareInputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + if (isObject(configuration)) { + configuration.interval = { + startInterval: configuration.startInterval, + startIntervalTimeUnit: configuration.startIntervalTimeUnit, + endInterval: configuration.endInterval, + endIntervalTimeUnit: configuration.endIntervalTimeUnit + }; + } + + return { + latestTsKeyNames: isDefinedAndNotNull(configuration?.latestTsKeyNames) ? configuration.latestTsKeyNames : null, + aggregation: isDefinedAndNotNull(configuration?.aggregation) ? configuration.aggregation : AggregationType.NONE, + fetchMode: isDefinedAndNotNull(configuration?.fetchMode) ? configuration.fetchMode : FetchMode.FIRST, + orderBy: isDefinedAndNotNull(configuration?.orderBy) ? configuration.orderBy : SamplingOrder.ASC, + limit: isDefinedAndNotNull(configuration?.limit) ? configuration.limit : 1000, + useMetadataIntervalPatterns: isDefinedAndNotNull(configuration?.useMetadataIntervalPatterns) ? + configuration.useMetadataIntervalPatterns : false, + interval: { + startInterval: isDefinedAndNotNull(configuration?.interval?.startInterval) ? configuration.interval.startInterval : 2, + startIntervalTimeUnit: isDefinedAndNotNull(configuration?.interval?.startIntervalTimeUnit) ? + configuration.interval.startIntervalTimeUnit : TimeUnit.MINUTES, + endInterval: isDefinedAndNotNull(configuration?.interval?.endInterval) ? configuration.interval.endInterval : 1, + endIntervalTimeUnit: isDefinedAndNotNull(configuration?.interval?.endIntervalTimeUnit) ? + configuration.interval.endIntervalTimeUnit : TimeUnit.MINUTES, + }, + startIntervalPattern: isDefinedAndNotNull(configuration?.startIntervalPattern) ? configuration.startIntervalPattern : null, + endIntervalPattern: isDefinedAndNotNull(configuration?.endIntervalPattern) ? configuration.endIntervalPattern : null + }; + } + + protected updateValidators(emitEvent: boolean) { + const fetchMode: FetchMode = this.getTelemetryFromDatabaseConfigForm.get('fetchMode').value; + const useMetadataIntervalPatterns: boolean = this.getTelemetryFromDatabaseConfigForm.get('useMetadataIntervalPatterns').value; + if (fetchMode && fetchMode === FetchMode.ALL) { + this.getTelemetryFromDatabaseConfigForm.get('aggregation').setValidators([Validators.required]); + this.getTelemetryFromDatabaseConfigForm.get('orderBy').setValidators([Validators.required]); + this.getTelemetryFromDatabaseConfigForm.get('limit').setValidators([Validators.required, Validators.min(2), Validators.max(1000)]); + } else { + this.getTelemetryFromDatabaseConfigForm.get('aggregation').setValidators([]); + this.getTelemetryFromDatabaseConfigForm.get('orderBy').setValidators([]); + this.getTelemetryFromDatabaseConfigForm.get('limit').setValidators([]); + } + if (useMetadataIntervalPatterns) { + this.getTelemetryFromDatabaseConfigForm.get('interval.startInterval').setValidators([]); + this.getTelemetryFromDatabaseConfigForm.get('interval.startIntervalTimeUnit').setValidators([]); + this.getTelemetryFromDatabaseConfigForm.get('interval.endInterval').setValidators([]); + this.getTelemetryFromDatabaseConfigForm.get('interval.endIntervalTimeUnit').setValidators([]); + this.getTelemetryFromDatabaseConfigForm.get('interval').setValidators([]); + this.getTelemetryFromDatabaseConfigForm.get('startIntervalPattern').setValidators([Validators.required, + Validators.pattern(/(?:.|\s)*\S(&:.|\s)*/)]); + this.getTelemetryFromDatabaseConfigForm.get('endIntervalPattern').setValidators([Validators.required, + Validators.pattern(/(?:.|\s)*\S(&:.|\s)*/)]); + } else { + this.getTelemetryFromDatabaseConfigForm.get('interval.startInterval').setValidators([Validators.required, + Validators.min(1), Validators.max(2147483647)]); + this.getTelemetryFromDatabaseConfigForm.get('interval.startIntervalTimeUnit').setValidators([Validators.required]); + this.getTelemetryFromDatabaseConfigForm.get('interval.endInterval').setValidators([Validators.required, + Validators.min(1), Validators.max(2147483647)]); + this.getTelemetryFromDatabaseConfigForm.get('interval.endIntervalTimeUnit').setValidators([Validators.required]); + this.getTelemetryFromDatabaseConfigForm.get('interval').setValidators([this.intervalValidator()]); + this.getTelemetryFromDatabaseConfigForm.get('startIntervalPattern').setValidators([]); + this.getTelemetryFromDatabaseConfigForm.get('endIntervalPattern').setValidators([]); + } + this.getTelemetryFromDatabaseConfigForm.get('aggregation').updateValueAndValidity({emitEvent}); + this.getTelemetryFromDatabaseConfigForm.get('orderBy').updateValueAndValidity({emitEvent}); + this.getTelemetryFromDatabaseConfigForm.get('limit').updateValueAndValidity({emitEvent}); + this.getTelemetryFromDatabaseConfigForm.get('interval.startInterval').updateValueAndValidity({emitEvent}); + this.getTelemetryFromDatabaseConfigForm.get('interval.startIntervalTimeUnit').updateValueAndValidity({emitEvent}); + this.getTelemetryFromDatabaseConfigForm.get('interval.endInterval').updateValueAndValidity({emitEvent}); + this.getTelemetryFromDatabaseConfigForm.get('interval.endIntervalTimeUnit').updateValueAndValidity({emitEvent}); + this.getTelemetryFromDatabaseConfigForm.get('interval').updateValueAndValidity({emitEvent}); + this.getTelemetryFromDatabaseConfigForm.get('startIntervalPattern').updateValueAndValidity({emitEvent}); + this.getTelemetryFromDatabaseConfigForm.get('endIntervalPattern').updateValueAndValidity({emitEvent}); + } + + public defaultPaddingEnable() { + return this.getTelemetryFromDatabaseConfigForm.get('fetchMode').value === FetchMode.ALL && + this.getTelemetryFromDatabaseConfigForm.get('aggregation').value === AggregationType.NONE; + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/enrichment/originator-attributes-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/originator-attributes-config.component.html new file mode 100644 index 0000000000..6c767c3664 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/originator-attributes-config.component.html @@ -0,0 +1,40 @@ + +
    +
    +
    +
    rule-node-config.originator-attributes
    +
    + rule-node-config.at-least-one-field-required +
    +
    + + + + +
    +
    + + {{ 'rule-node-config.tell-failure' | translate }} + +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/enrichment/originator-attributes-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/originator-attributes-config.component.ts new file mode 100644 index 0000000000..b0ff9e0098 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/originator-attributes-config.component.ts @@ -0,0 +1,75 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { isDefinedAndNotNull, isObject, } from '@core/public-api'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@app/shared/models/rule-node.models'; +import { FetchTo } from '@home/components/rule-node/rule-node-config.models'; + +@Component({ + selector: 'tb-enrichment-node-originator-attributes-config', + templateUrl: './originator-attributes-config.component.html', + styleUrls: [] +}) +export class OriginatorAttributesConfigComponent extends RuleNodeConfigurationComponent { + + originatorAttributesConfigForm: FormGroup; + + constructor(public translate: TranslateService, + private fb: FormBuilder) { + super(); + } + + protected configForm(): FormGroup { + return this.originatorAttributesConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.originatorAttributesConfigForm = this.fb.group({ + tellFailureIfAbsent: [configuration.tellFailureIfAbsent, []], + fetchTo: [configuration.fetchTo, []], + attributesControl: [configuration.attributesControl, []] + }); + } + + protected prepareInputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + if (isObject(configuration)) { + configuration.attributesControl = { + clientAttributeNames: isDefinedAndNotNull(configuration?.clientAttributeNames) ? configuration.clientAttributeNames : [], + latestTsKeyNames: isDefinedAndNotNull(configuration?.latestTsKeyNames) ? configuration.latestTsKeyNames : [], + serverAttributeNames: isDefinedAndNotNull(configuration?.serverAttributeNames) ? configuration.serverAttributeNames : [], + sharedAttributeNames: isDefinedAndNotNull(configuration?.sharedAttributeNames) ? configuration.sharedAttributeNames : [], + getLatestValueWithTs: isDefinedAndNotNull(configuration?.getLatestValueWithTs) ? configuration.getLatestValueWithTs : false + }; + } + + return { + fetchTo: isDefinedAndNotNull(configuration?.fetchTo) ? configuration.fetchTo : FetchTo.METADATA, + tellFailureIfAbsent: isDefinedAndNotNull(configuration?.tellFailureIfAbsent) ? configuration.tellFailureIfAbsent : false, + attributesControl: isDefinedAndNotNull(configuration?.attributesControl) ? configuration.attributesControl : null + }; + } + + protected prepareOutputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + for (const key of Object.keys(configuration.attributesControl)) { + configuration[key] = configuration.attributesControl[key]; + } + delete configuration.attributesControl; + return configuration; + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/enrichment/originator-fields-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/originator-fields-config.component.html new file mode 100644 index 0000000000..4f5ed6cf13 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/originator-fields-config.component.html @@ -0,0 +1,41 @@ + +
    + + + + +
    + + {{ 'rule-node-config.skip-empty-fields' | translate }} + +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/enrichment/originator-fields-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/originator-fields-config.component.ts new file mode 100644 index 0000000000..d291bb56d3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/originator-fields-config.component.ts @@ -0,0 +1,67 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { deepTrim, isDefinedAndNotNull } from '@core/public-api'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; +import { allowedOriginatorFields, FetchTo, SvMapOption } from '@home/components/rule-node/rule-node-config.models'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@app/shared/models/rule-node.models'; + +@Component({ + selector: 'tb-enrichment-node-originator-fields-config', + templateUrl: './originator-fields-config.component.html' +}) +export class OriginatorFieldsConfigComponent extends RuleNodeConfigurationComponent { + + originatorFieldsConfigForm: FormGroup; + public originatorFields: SvMapOption[] = []; + + constructor(private fb: FormBuilder, + private translate: TranslateService) { + super(); + for (const field of allowedOriginatorFields) { + this.originatorFields.push({ + value: field.value, + name: this.translate.instant(field.name) + }); + } + } + + protected configForm(): FormGroup { + return this.originatorFieldsConfigForm; + } + + protected prepareOutputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + return deepTrim(configuration); + } + + protected prepareInputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + return { + dataMapping: isDefinedAndNotNull(configuration?.dataMapping) ? configuration.dataMapping : null, + ignoreNullStrings: isDefinedAndNotNull(configuration?.ignoreNullStrings) ? configuration.ignoreNullStrings : null, + fetchTo: isDefinedAndNotNull(configuration?.fetchTo) ? configuration.fetchTo : FetchTo.METADATA + }; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.originatorFieldsConfigForm = this.fb.group({ + dataMapping: [configuration.dataMapping, [Validators.required]], + ignoreNullStrings: [configuration.ignoreNullStrings, []], + fetchTo: [configuration.fetchTo, []] + }); + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/enrichment/related-attributes-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/related-attributes-config.component.html new file mode 100644 index 0000000000..677ffb52eb --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/related-attributes-config.component.html @@ -0,0 +1,62 @@ + +
    + + +
    +
    rule-node-config.data-to-fetch
    + + + {{ data.name }} + + + + + + + + +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/enrichment/related-attributes-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/related-attributes-config.component.ts new file mode 100644 index 0000000000..8446479cf4 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/related-attributes-config.component.ts @@ -0,0 +1,160 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { deepTrim, isDefinedAndNotNull } from '@core/public-api'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@app/shared/models/rule-node.models'; +import { + allowedOriginatorFields, + DataToFetch, + dataToFetchTranslations, + FetchTo, + msgMetadataLabelTranslations, + SvMapOption +} from '../rule-node-config.models'; +import { entityFields } from '@shared/models/entity.models'; + +@Component({ + selector: 'tb-enrichment-node-related-attributes-config', + templateUrl: './related-attributes-config.component.html', + styleUrls: [] +}) +export class RelatedAttributesConfigComponent extends RuleNodeConfigurationComponent { + + relatedAttributesConfigForm: FormGroup; + + protected readonly DataToFetch = DataToFetch; + + public msgMetadataLabelTranslations = msgMetadataLabelTranslations; + public originatorFields: SvMapOption[] = []; + public fetchToData = []; + + constructor(private fb: FormBuilder, + private translate: TranslateService) { + super(); + for (const field of Object.keys(allowedOriginatorFields)) { + this.originatorFields.push({ + value: allowedOriginatorFields[field].value, + name: this.translate.instant(allowedOriginatorFields[field].name) + }); + } + for (const key of dataToFetchTranslations.keys()) { + this.fetchToData.push({ + value: key, + name: this.translate.instant(dataToFetchTranslations.get(key as DataToFetch)) + }); + } + } + + protected configForm(): FormGroup { + return this.relatedAttributesConfigForm; + } + + protected prepareOutputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + if (configuration.dataToFetch === DataToFetch.FIELDS) { + configuration.dataMapping = configuration.svMap; + delete configuration.svMap; + } else { + configuration.dataMapping = configuration.kvMap; + delete configuration.kvMap; + } + + const filteDataMapping = {}; + if (configuration && configuration.dataMapping) { + for (const key of Object.keys(configuration.dataMapping)) { + filteDataMapping[key.trim()] = configuration.dataMapping[key]; + } + } + configuration.dataMapping = filteDataMapping; + delete configuration.svMap; + delete configuration.kvMap; + + return deepTrim(configuration); + } + + protected prepareInputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + let svMap = { + [entityFields.name.value]: `relatedEntity${this.translate.instant(entityFields.name.name)}` + }; + let kvMap = { + serialNumber: 'sn' + }; + + let dataToFetch: DataToFetch; + if (isDefinedAndNotNull(configuration?.telemetry)) { + dataToFetch = configuration.telemetry ? DataToFetch.LATEST_TELEMETRY : DataToFetch.ATTRIBUTES; + } else { + dataToFetch = isDefinedAndNotNull(configuration?.dataToFetch) ? configuration.dataToFetch : DataToFetch.ATTRIBUTES; + } + + let dataMapping; + if (isDefinedAndNotNull(configuration?.attrMapping)) { + dataMapping = configuration.attrMapping; + } else { + dataMapping = isDefinedAndNotNull(configuration?.dataMapping) ? configuration.dataMapping : null; + } + + if (dataToFetch === DataToFetch.FIELDS) { + svMap = dataMapping; + } else { + kvMap = dataMapping; + } + + return { + relationsQuery: isDefinedAndNotNull(configuration?.relationsQuery) ? configuration.relationsQuery : null, + dataToFetch, + svMap, + kvMap, + fetchTo: isDefinedAndNotNull(configuration?.fetchTo) ? configuration.fetchTo : FetchTo.METADATA + }; + } + + public selectTranslation(latestTelemetryTranslation: string, attributesTranslation: string) { + if (this.relatedAttributesConfigForm.get('dataToFetch').value === DataToFetch.LATEST_TELEMETRY) { + return latestTelemetryTranslation; + } else { + return attributesTranslation; + } + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.relatedAttributesConfigForm = this.fb.group({ + relationsQuery: [configuration.relationsQuery, [Validators.required]], + dataToFetch: [configuration.dataToFetch, []], + kvMap: [configuration.kvMap, [Validators.required]], + svMap: [configuration.svMap, [Validators.required]], + fetchTo: [configuration.fetchTo, []] + }); + } + + protected validatorTriggers(): string[] { + return ['dataToFetch']; + } + + protected updateValidators(emitEvent: boolean) { + if (this.relatedAttributesConfigForm.get('dataToFetch').value === DataToFetch.FIELDS) { + this.relatedAttributesConfigForm.get('svMap').enable({emitEvent: false}); + this.relatedAttributesConfigForm.get('kvMap').disable({emitEvent: false}); + this.relatedAttributesConfigForm.get('svMap').updateValueAndValidity(); + } else { + this.relatedAttributesConfigForm.get('svMap').disable({emitEvent: false}); + this.relatedAttributesConfigForm.get('kvMap').enable({emitEvent: false}); + this.relatedAttributesConfigForm.get('kvMap').updateValueAndValidity(); + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/enrichment/tenant-attributes-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/tenant-attributes-config.component.html new file mode 100644 index 0000000000..c690e2b855 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/tenant-attributes-config.component.html @@ -0,0 +1,45 @@ + +
    +
    rule-node-config.mapping-of-tenant
    +
    +
    + + + {{ data.name }} + + +
    +
    + + + + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/enrichment/tenant-attributes-config.component.scss b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/tenant-attributes-config.component.scss new file mode 100644 index 0000000000..4943a8fa07 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/tenant-attributes-config.component.scss @@ -0,0 +1,21 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + .fetch-to-data-toggle { + max-width: 420px; + width: 100%; + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/enrichment/tenant-attributes-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/tenant-attributes-config.component.ts new file mode 100644 index 0000000000..2c05d3369f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/tenant-attributes-config.component.ts @@ -0,0 +1,91 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { isDefinedAndNotNull } from '@core/public-api'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@app/shared/models/rule-node.models'; +import { DataToFetch, dataToFetchTranslations, FetchTo } from '../rule-node-config.models'; + +@Component({ + selector: 'tb-enrichment-node-tenant-attributes-config', + templateUrl: './tenant-attributes-config.component.html', + styleUrls: ['./tenant-attributes-config.component.scss'] +}) +export class TenantAttributesConfigComponent extends RuleNodeConfigurationComponent { + + tenantAttributesConfigForm: FormGroup; + public fetchToData = []; + + constructor(private fb: FormBuilder, + private translate: TranslateService) { + super(); + for (const key of dataToFetchTranslations.keys()) { + if (key !== DataToFetch.FIELDS) { + this.fetchToData.push({ + value: key, + name: this.translate.instant(dataToFetchTranslations.get(key as DataToFetch)) + }); + } + } + } + + protected configForm(): FormGroup { + return this.tenantAttributesConfigForm; + } + + protected prepareInputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + let dataToFetch: DataToFetch; + if (isDefinedAndNotNull(configuration?.telemetry)) { + dataToFetch = configuration.telemetry ? DataToFetch.LATEST_TELEMETRY : DataToFetch.ATTRIBUTES; + } else { + dataToFetch = isDefinedAndNotNull(configuration?.dataToFetch) ? configuration.dataToFetch : DataToFetch.ATTRIBUTES; + } + + let dataMapping; + if (isDefinedAndNotNull(configuration?.attrMapping)) { + dataMapping = configuration.attrMapping; + } else { + dataMapping = isDefinedAndNotNull(configuration?.dataMapping) ? configuration.dataMapping : null; + } + + return { + dataToFetch, + dataMapping, + fetchTo: isDefinedAndNotNull(configuration?.fetchTo) ? configuration.fetchTo : FetchTo.METADATA + }; + } + + public selectTranslation(latestTelemetryTranslation: string, attributesTranslation: string) { + if (this.tenantAttributesConfigForm.get('dataToFetch').value === DataToFetch.LATEST_TELEMETRY) { + return latestTelemetryTranslation; + } else { + return attributesTranslation; + } + } + + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.tenantAttributesConfigForm = this.fb.group({ + dataToFetch: [configuration.dataToFetch, []], + dataMapping: [configuration.dataMapping, [Validators.required]], + fetchTo: [configuration.fetchTo, []] + }); + } + + protected readonly DataToFetch = DataToFetch; +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/azure-iot-hub-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/external/azure-iot-hub-config.component.html new file mode 100644 index 0000000000..59dcb40fec --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/azure-iot-hub-config.component.html @@ -0,0 +1,122 @@ + +
    + + rule-node-config.topic + + + {{ 'rule-node-config.topic-required' | translate }} + + rule-node-config.general-pattern-hint + + + rule-node-config.hostname + + + {{ 'rule-node-config.hostname-required' | translate }} + + + + rule-node-config.device-id + + + {{ 'rule-node-config.device-id-required' | translate }} + + + + + + rule-node-config.credentials + + {{ azureIotHubCredentialsTypeTranslationsMap.get(azureIotHubConfigForm.get('credentials.type').value) | translate }} + + +
    + + rule-node-config.credentials-type + + + {{ azureIotHubCredentialsTypeTranslationsMap.get(credentialsType) | translate }} + + + + {{ 'rule-node-config.credentials-type-required' | translate }} + + +
    + + + + + rule-node-config.sas-key + + + + {{ 'rule-node-config.sas-key-required' | translate }} + + + + + + + + + + + + + + rule-node-config.private-key-password + + + + +
    +
    +
    +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/azure-iot-hub-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/external/azure-iot-hub-config.component.ts new file mode 100644 index 0000000000..72864610f5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/azure-iot-hub-config.component.ts @@ -0,0 +1,117 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@app/shared/models/rule-node.models'; +import { + AzureIotHubCredentialsType, + azureIotHubCredentialsTypes, + azureIotHubCredentialsTypeTranslations +} from '@home/components/rule-node/rule-node-config.models'; + +@Component({ + selector: 'tb-external-node-azure-iot-hub-config', + templateUrl: './azure-iot-hub-config.component.html', + styleUrls: ['./mqtt-config.component.scss'] +}) +export class AzureIotHubConfigComponent extends RuleNodeConfigurationComponent { + + azureIotHubConfigForm: UntypedFormGroup; + + allAzureIotHubCredentialsTypes = azureIotHubCredentialsTypes; + azureIotHubCredentialsTypeTranslationsMap = azureIotHubCredentialsTypeTranslations; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.azureIotHubConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.azureIotHubConfigForm = this.fb.group({ + topicPattern: [configuration ? configuration.topicPattern : null, [Validators.required]], + host: [configuration ? configuration.host : null, [Validators.required]], + port: [configuration ? configuration.port : null, [Validators.required, Validators.min(1), Validators.max(65535)]], + connectTimeoutSec: [configuration ? configuration.connectTimeoutSec : null, + [Validators.required, Validators.min(1), Validators.max(200)]], + clientId: [configuration ? configuration.clientId : null, [Validators.required]], + cleanSession: [configuration ? configuration.cleanSession : false, []], + ssl: [configuration ? configuration.ssl : false, []], + credentials: this.fb.group( + { + type: [configuration && configuration.credentials ? configuration.credentials.type : null, [Validators.required]], + sasKey: [configuration && configuration.credentials ? configuration.credentials.sasKey : null, []], + caCert: [configuration && configuration.credentials ? configuration.credentials.caCert : null, []], + caCertFileName: [configuration && configuration.credentials ? configuration.credentials.caCertFileName : null, []], + privateKey: [configuration && configuration.credentials ? configuration.credentials.privateKey : null, []], + privateKeyFileName: [configuration && configuration.credentials ? configuration.credentials.privateKeyFileName : null, []], + cert: [configuration && configuration.credentials ? configuration.credentials.cert : null, []], + certFileName: [configuration && configuration.credentials ? configuration.credentials.certFileName : null, []], + password: [configuration && configuration.credentials ? configuration.credentials.password : null, []], + } + ) + }); + } + + protected prepareOutputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + const credentialsType: AzureIotHubCredentialsType = configuration.credentials.type; + if (credentialsType === 'sas') { + configuration.credentials = { + type: credentialsType, + sasKey: configuration.credentials.sasKey, + caCert: configuration.credentials.caCert, + caCertFileName: configuration.credentials.caCertFileName + }; + } + return configuration; + } + + protected validatorTriggers(): string[] { + return ['credentials.type']; + } + + protected updateValidators(emitEvent: boolean) { + const credentialsControl = this.azureIotHubConfigForm.get('credentials'); + const credentialsType: AzureIotHubCredentialsType = credentialsControl.get('type').value; + if (emitEvent) { + credentialsControl.reset({ type: credentialsType }, {emitEvent: false}); + } + credentialsControl.get('sasKey').setValidators([]); + credentialsControl.get('privateKey').setValidators([]); + credentialsControl.get('privateKeyFileName').setValidators([]); + credentialsControl.get('cert').setValidators([]); + credentialsControl.get('certFileName').setValidators([]); + switch (credentialsType) { + case 'sas': + credentialsControl.get('sasKey').setValidators([Validators.required]); + break; + case 'cert.PEM': + credentialsControl.get('privateKey').setValidators([Validators.required]); + credentialsControl.get('privateKeyFileName').setValidators([Validators.required]); + credentialsControl.get('cert').setValidators([Validators.required]); + credentialsControl.get('certFileName').setValidators([Validators.required]); + break; + } + credentialsControl.get('sasKey').updateValueAndValidity({emitEvent}); + credentialsControl.get('privateKey').updateValueAndValidity({emitEvent}); + credentialsControl.get('privateKeyFileName').updateValueAndValidity({emitEvent}); + credentialsControl.get('cert').updateValueAndValidity({emitEvent}); + credentialsControl.get('certFileName').updateValueAndValidity({emitEvent}); + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/external-rule-node-config.module.ts b/ui-ngx/src/app/modules/home/components/rule-node/external/external-rule-node-config.module.ts new file mode 100644 index 0000000000..ef550d8460 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/external-rule-node-config.module.ts @@ -0,0 +1,75 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { NgModule } from '@angular/core'; +import { SnsConfigComponent } from './sns-config.component'; +import { SqsConfigComponent } from './sqs-config.component'; +import { PubSubConfigComponent } from './pubsub-config.component'; +import { KafkaConfigComponent } from './kafka-config.component'; +import { MqttConfigComponent } from './mqtt-config.component'; +import { NotificationConfigComponent } from './notification-config.component'; +import { RabbitMqConfigComponent } from './rabbit-mq-config.component'; +import { RestApiCallConfigComponent } from './rest-api-call-config.component'; +import { SendEmailConfigComponent } from './send-email-config.component'; +import { AzureIotHubConfigComponent } from './azure-iot-hub-config.component'; +import { SendSmsConfigComponent } from './send-sms-config.component'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@shared/public-api'; +import { HomeComponentsModule } from '@home/components/public-api'; +import { CommonRuleNodeConfigModule } from '../common/common-rule-node-config.module'; +import { SlackConfigComponent } from './slack-config.component'; +import { LambdaConfigComponent } from './lambda-config.component'; + +@NgModule({ + declarations: [ + SnsConfigComponent, + SqsConfigComponent, + LambdaConfigComponent, + PubSubConfigComponent, + KafkaConfigComponent, + MqttConfigComponent, + NotificationConfigComponent, + RabbitMqConfigComponent, + RestApiCallConfigComponent, + SendEmailConfigComponent, + AzureIotHubConfigComponent, + SendSmsConfigComponent, + SlackConfigComponent + ], + imports: [ + CommonModule, + SharedModule, + HomeComponentsModule, + CommonRuleNodeConfigModule + ], + exports: [ + SnsConfigComponent, + SqsConfigComponent, + LambdaConfigComponent, + PubSubConfigComponent, + KafkaConfigComponent, + MqttConfigComponent, + NotificationConfigComponent, + RabbitMqConfigComponent, + RestApiCallConfigComponent, + SendEmailConfigComponent, + AzureIotHubConfigComponent, + SendSmsConfigComponent, + SlackConfigComponent + ] +}) +export class ExternalRuleNodeConfigModule { +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/kafka-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/external/kafka-config.component.html new file mode 100644 index 0000000000..f23bbd353c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/kafka-config.component.html @@ -0,0 +1,111 @@ + +
    + + rule-node-config.topic-pattern + + + {{ 'rule-node-config.topic-pattern-required' | translate }} + + rule-node-config.general-pattern-hint + + + rule-node-config.key-pattern + + rule-node-config.general-pattern-hint + +
    rule-node-config.key-pattern-hint
    + + rule-node-config.bootstrap-servers + + + {{ 'rule-node-config.bootstrap-servers-required' | translate }} + + + + rule-node-config.retries + + + {{ 'rule-node-config.min-retries-message' | translate }} + + + + rule-node-config.batch-size-bytes + + + {{ 'rule-node-config.min-batch-size-bytes-message' | translate }} + + + + rule-node-config.linger-ms + + + {{ 'rule-node-config.min-linger-ms-message' | translate }} + + + + rule-node-config.buffer-memory-bytes + + + {{ 'rule-node-config.min-buffer-memory-bytes-message' | translate }} + + + + rule-node-config.acks + + + {{ ackValue }} + + + + + rule-node-config.key-serializer + + + {{ 'rule-node-config.key-serializer-required' | translate }} + + + + rule-node-config.value-serializer + + + {{ 'rule-node-config.value-serializer-required' | translate }} + + + + + + + {{ 'rule-node-config.add-metadata-key-values-as-kafka-headers' | translate }} + +
    rule-node-config.add-metadata-key-values-as-kafka-headers-hint
    + + rule-node-config.charset-encoding + + + {{ ToByteStandartCharsetTypeTranslationMap.get(charset) | translate }} + + + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/kafka-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/external/kafka-config.component.ts new file mode 100644 index 0000000000..d0f97a8f9d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/kafka-config.component.ts @@ -0,0 +1,79 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; +import { + ToByteStandartCharsetTypes, + ToByteStandartCharsetTypeTranslations +} from '@home/components/rule-node/rule-node-config.models'; + +@Component({ + selector: 'tb-external-node-kafka-config', + templateUrl: './kafka-config.component.html', + styleUrls: [] +}) +export class KafkaConfigComponent extends RuleNodeConfigurationComponent { + + kafkaConfigForm: UntypedFormGroup; + + ackValues: string[] = ['all', '-1', '0', '1']; + + ToByteStandartCharsetTypesValues = ToByteStandartCharsetTypes; + ToByteStandartCharsetTypeTranslationMap = ToByteStandartCharsetTypeTranslations; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.kafkaConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.kafkaConfigForm = this.fb.group({ + topicPattern: [configuration ? configuration.topicPattern : null, [Validators.required]], + keyPattern: [configuration ? configuration.keyPattern : null], + bootstrapServers: [configuration ? configuration.bootstrapServers : null, [Validators.required]], + retries: [configuration ? configuration.retries : null, [Validators.min(0)]], + batchSize: [configuration ? configuration.batchSize : null, [Validators.min(0)]], + linger: [configuration ? configuration.linger : null, [Validators.min(0)]], + bufferMemory: [configuration ? configuration.bufferMemory : null, [Validators.min(0)]], + acks: [configuration ? configuration.acks : null, [Validators.required]], + keySerializer: [configuration ? configuration.keySerializer : null, [Validators.required]], + valueSerializer: [configuration ? configuration.valueSerializer : null, [Validators.required]], + otherProperties: [configuration ? configuration.otherProperties : null, []], + addMetadataKeyValuesAsKafkaHeaders: [configuration ? configuration.addMetadataKeyValuesAsKafkaHeaders : false, []], + kafkaHeadersCharset: [configuration ? configuration.kafkaHeadersCharset : null, []] + }); + } + + protected validatorTriggers(): string[] { + return ['addMetadataKeyValuesAsKafkaHeaders']; + } + + protected updateValidators(emitEvent: boolean) { + const addMetadataKeyValuesAsKafkaHeaders: boolean = this.kafkaConfigForm.get('addMetadataKeyValuesAsKafkaHeaders').value; + if (addMetadataKeyValuesAsKafkaHeaders) { + this.kafkaConfigForm.get('kafkaHeadersCharset').setValidators([Validators.required]); + } else { + this.kafkaConfigForm.get('kafkaHeadersCharset').setValidators([]); + } + this.kafkaConfigForm.get('kafkaHeadersCharset').updateValueAndValidity({emitEvent}); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/lambda-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/external/lambda-config.component.html new file mode 100644 index 0000000000..2ea8fa3957 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/lambda-config.component.html @@ -0,0 +1,115 @@ + +
    +
    +
    +
    rule-node-config.function-configuration
    +
    + + +
    + + {{'rule-node-config.function-name' | translate}} + + + {{'rule-node-config.function-name-required' | translate}} + + + + {{'rule-node-config.qualifier' | translate}} + + rule-node-config.qualifier-hint + +
    +
    + +
    + + + rule-node-config.aws-credentials + +
    + + rule-node-config.aws-access-key-id + + + {{ 'rule-node-config.aws-access-key-id-required' | translate }} + + + + rule-node-config.aws-secret-access-key + + + {{ 'rule-node-config.aws-secret-access-key-required' | translate }} + + + + rule-node-config.aws-region + + + {{ 'rule-node-config.aws-region-required' | translate }} + + +
    +
    +
    +
    + + + rule-node-config.advanced-settings + +
    +
    + + rule-node-config.connection-timeout + + + {{ 'rule-node-config.connection-timeout-required' | translate }} + + + {{ 'rule-node-config.connection-timeout-min' | translate }} + + help + + + rule-node-config.request-timeout + + + {{ 'rule-node-config.request-timeout-required' | translate }} + + + {{ 'rule-node-config.request-timeout-min' | translate }} + + help + +
    +
    + + {{ 'rule-node-config.tell-failure-aws-lambda' | translate }} + +
    +
    +
    +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/lambda-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/external/lambda-config.component.ts new file mode 100644 index 0000000000..3270e3b51e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/lambda-config.component.ts @@ -0,0 +1,50 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; + +@Component({ + selector: 'tb-external-node-lambda-config', + templateUrl: './lambda-config.component.html', + styleUrls: [] +}) +export class LambdaConfigComponent extends RuleNodeConfigurationComponent { + + lambdaConfigForm: UntypedFormGroup; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.lambdaConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.lambdaConfigForm = this.fb.group({ + functionName: [configuration ? configuration.functionName : null, [Validators.required]], + qualifier: [configuration ? configuration.qualifier : null, []], + accessKey: [configuration ? configuration.accessKey : null, [Validators.required]], + secretKey: [configuration ? configuration.secretKey : null, [Validators.required]], + region: [configuration ? configuration.region : null, [Validators.required]], + connectionTimeout: [configuration ? configuration.connectionTimeout : null, [Validators.required, Validators.min(0)]], + requestTimeout: [configuration ? configuration.requestTimeout : null, [Validators.required, Validators.min(0)]], + tellFailureIfFuncThrowsExc: [configuration ? configuration.tellFailureIfFuncThrowsExc : false, []] + }); + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/mqtt-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/external/mqtt-config.component.html new file mode 100644 index 0000000000..a57da12587 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/mqtt-config.component.html @@ -0,0 +1,85 @@ + +
    + + rule-node-config.topic-pattern + + + {{ 'rule-node-config.topic-pattern-required' | translate }} + + rule-node-config.general-pattern-hint + +
    + + rule-node-config.host + + + {{ 'rule-node-config.host-required' | translate }} + + + + rule-node-config.port + + + {{ 'rule-node-config.port-required' | translate }} + + + {{ 'rule-node-config.port-range' | translate }} + + + {{ 'rule-node-config.port-range' | translate }} + + + + rule-node-config.connect-timeout + + + {{ 'rule-node-config.connect-timeout-required' | translate }} + + + {{ 'rule-node-config.connect-timeout-range' | translate }} + + + {{ 'rule-node-config.connect-timeout-range' | translate }} + + +
    + + rule-node-config.client-id + + {{'rule-node-config.client-id-hint' | translate}} + + + {{ 'rule-node-config.append-client-id-suffix' | translate }} + +
    {{ "rule-node-config.client-id-suffix-hint" | translate }}
    + + {{ 'rule-node-config.parse-to-plain-text' | translate }} + +
    {{ "rule-node-config.parse-to-plain-text-hint" | translate }}
    + + {{ 'rule-node-config.clean-session' | translate }} + + + {{ "rule-node-config.retained-message" | translate }} + + + {{ 'rule-node-config.enable-ssl' | translate }} + + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/mqtt-config.component.scss b/ui-ngx/src/app/modules/home/components/rule-node/external/mqtt-config.component.scss new file mode 100644 index 0000000000..0bd9d8e62c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/mqtt-config.component.scss @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + .tb-mqtt-credentials-panel-group { + margin: 0 6px; + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/mqtt-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/external/mqtt-config.component.ts new file mode 100644 index 0000000000..7edfc6f14d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/mqtt-config.component.ts @@ -0,0 +1,71 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { isNotEmptyStr } from '@core/public-api'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; + +@Component({ + selector: 'tb-external-node-mqtt-config', + templateUrl: './mqtt-config.component.html', + styleUrls: ['./mqtt-config.component.scss'] +}) +export class MqttConfigComponent extends RuleNodeConfigurationComponent { + + mqttConfigForm: UntypedFormGroup; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.mqttConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.mqttConfigForm = this.fb.group({ + topicPattern: [configuration ? configuration.topicPattern : null, [Validators.required]], + host: [configuration ? configuration.host : null, [Validators.required]], + port: [configuration ? configuration.port : null, [Validators.required, Validators.min(1), Validators.max(65535)]], + connectTimeoutSec: [configuration ? configuration.connectTimeoutSec : null, + [Validators.required, Validators.min(1), Validators.max(200)]], + clientId: [configuration ? configuration.clientId : null, []], + appendClientIdSuffix: [{ + value: configuration ? configuration.appendClientIdSuffix : false, + disabled: !(configuration && isNotEmptyStr(configuration.clientId)) + }, []], + parseToPlainText: [configuration ? configuration.parseToPlainText : false, []], + cleanSession: [configuration ? configuration.cleanSession : false, []], + retainedMessage: [configuration ? configuration.retainedMessage : false, []], + ssl: [configuration ? configuration.ssl : false, []], + credentials: [configuration ? configuration.credentials : null, []] + }); + } + + protected updateValidators(emitEvent: boolean) { + if (isNotEmptyStr(this.mqttConfigForm.get('clientId').value)) { + this.mqttConfigForm.get('appendClientIdSuffix').enable({emitEvent: false}); + } else { + this.mqttConfigForm.get('appendClientIdSuffix').disable({emitEvent: false}); + } + this.mqttConfigForm.get('appendClientIdSuffix').updateValueAndValidity({emitEvent}); + } + + protected validatorTriggers(): string[] { + return ['clientId']; + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/notification-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/external/notification-config.component.html new file mode 100644 index 0000000000..ee031b1237 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/notification-config.component.html @@ -0,0 +1,34 @@ + +
    + + + + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/notification-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/external/notification-config.component.ts new file mode 100644 index 0000000000..2b2fcfb319 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/notification-config.component.ts @@ -0,0 +1,48 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; +import { NotificationType } from '@shared/models/notification.models'; +import { EntityType } from '@shared/models/entity-type.models'; + +@Component({ + selector: 'tb-external-node-notification-config', + templateUrl: './notification-config.component.html', + styleUrls: [] +}) +export class NotificationConfigComponent extends RuleNodeConfigurationComponent { + + notificationConfigForm: FormGroup; + notificationType = NotificationType; + entityType = EntityType; + + constructor(private fb: FormBuilder) { + super(); + } + + protected configForm(): FormGroup { + return this.notificationConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.notificationConfigForm = this.fb.group({ + templateId: [configuration ? configuration.templateId : null, [Validators.required]], + targets: [configuration ? configuration.targets : [], [Validators.required]], + }); + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/pubsub-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/external/pubsub-config.component.html new file mode 100644 index 0000000000..b3d303765a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/pubsub-config.component.html @@ -0,0 +1,53 @@ + +
    + + rule-node-config.gcp-project-id + + + {{ 'rule-node-config.gcp-project-id-required' | translate }} + + + + rule-node-config.pubsub-topic-name + + + {{ 'rule-node-config.pubsub-topic-name-required' | translate }} + + + + + +
    + + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/pubsub-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/external/pubsub-config.component.ts new file mode 100644 index 0000000000..f7c65537e0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/pubsub-config.component.ts @@ -0,0 +1,47 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; + +@Component({ + selector: 'tb-external-node-pub-sub-config', + templateUrl: './pubsub-config.component.html', + styleUrls: [] +}) +export class PubSubConfigComponent extends RuleNodeConfigurationComponent { + + pubSubConfigForm: UntypedFormGroup; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.pubSubConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.pubSubConfigForm = this.fb.group({ + projectId: [configuration ? configuration.projectId : null, [Validators.required]], + topicName: [configuration ? configuration.topicName : null, [Validators.required]], + serviceAccountKey: [configuration ? configuration.serviceAccountKey : null, [Validators.required]], + serviceAccountKeyFileName: [configuration ? configuration.serviceAccountKeyFileName : null, [Validators.required]], + messageAttributes: [configuration ? configuration.messageAttributes : null, []] + }); + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/rabbit-mq-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/external/rabbit-mq-config.component.html new file mode 100644 index 0000000000..c04b0be902 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/rabbit-mq-config.component.html @@ -0,0 +1,96 @@ + +
    + + rule-node-config.exchange-name-pattern + + + + rule-node-config.routing-key-pattern + + + + rule-node-config.message-properties + + + {{ property }} + + + +
    + + rule-node-config.host + + + {{ 'rule-node-config.host-required' | translate }} + + + + rule-node-config.port + + + {{ 'rule-node-config.port-required' | translate }} + + + {{ 'rule-node-config.port-range' | translate }} + + + {{ 'rule-node-config.port-range' | translate }} + + +
    + + rule-node-config.virtual-host + + + + rule-node-config.username + + + + rule-node-config.password + + + + + {{ 'rule-node-config.automatic-recovery' | translate }} + + + rule-node-config.connection-timeout-ms + + + {{ 'rule-node-config.min-connection-timeout-ms-message' | translate }} + + + + rule-node-config.handshake-timeout-ms + + + {{ 'rule-node-config.min-handshake-timeout-ms-message' | translate }} + + + + + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/rabbit-mq-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/external/rabbit-mq-config.component.ts new file mode 100644 index 0000000000..89558bbe7f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/rabbit-mq-config.component.ts @@ -0,0 +1,64 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; + +@Component({ + selector: 'tb-external-node-rabbit-mq-config', + templateUrl: './rabbit-mq-config.component.html', + styleUrls: [] +}) +export class RabbitMqConfigComponent extends RuleNodeConfigurationComponent { + + rabbitMqConfigForm: UntypedFormGroup; + + messageProperties: string[] = [ + null, + 'BASIC', + 'TEXT_PLAIN', + 'MINIMAL_BASIC', + 'MINIMAL_PERSISTENT_BASIC', + 'PERSISTENT_BASIC', + 'PERSISTENT_TEXT_PLAIN' + ]; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.rabbitMqConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.rabbitMqConfigForm = this.fb.group({ + exchangeNamePattern: [configuration ? configuration.exchangeNamePattern : null, []], + routingKeyPattern: [configuration ? configuration.routingKeyPattern : null, []], + messageProperties: [configuration ? configuration.messageProperties : null, []], + host: [configuration ? configuration.host : null, [Validators.required]], + port: [configuration ? configuration.port : null, [Validators.required, Validators.min(1), Validators.max(65535)]], + virtualHost: [configuration ? configuration.virtualHost : null, []], + username: [configuration ? configuration.username : null, []], + password: [configuration ? configuration.password : null, []], + automaticRecoveryEnabled: [configuration ? configuration.automaticRecoveryEnabled : false, []], + connectionTimeout: [configuration ? configuration.connectionTimeout : null, [Validators.min(0)]], + handshakeTimeout: [configuration ? configuration.handshakeTimeout : null, [Validators.min(0)]], + clientProperties: [configuration ? configuration.clientProperties : null, []] + }); + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/rest-api-call-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/external/rest-api-call-config.component.html new file mode 100644 index 0000000000..0bae38471f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/rest-api-call-config.component.html @@ -0,0 +1,130 @@ + +
    + + rule-node-config.endpoint-url-pattern + + + {{ 'rule-node-config.endpoint-url-pattern-required' | translate }} + + rule-node-config.general-pattern-hint + + + rule-node-config.request-method + + + {{ requestType }} + + + + + {{ 'rule-node-config.enable-proxy' | translate }} + + + {{ 'rule-node-config.use-simple-client-http-factory' | translate }} + + + {{ 'rule-node-config.parse-to-plain-text' | translate }} + +
    rule-node-config.parse-to-plain-text-hint
    + + {{ 'rule-node-config.ignore-request-body' | translate }} + +
    + + {{ 'rule-node-config.use-system-proxy-properties' | translate }} + +
    +
    + + rule-node-config.proxy-scheme + + + {{ proxyScheme }} + + + + + rule-node-config.proxy-host + + + {{ 'rule-node-config.proxy-host-required' | translate }} + + + + rule-node-config.proxy-port + + + {{ 'rule-node-config.proxy-port-required' | translate }} + + + {{ 'rule-node-config.proxy-port-range' | translate }} + + +
    + + rule-node-config.proxy-user + + + + rule-node-config.proxy-password + + +
    +
    + + rule-node-config.read-timeout + + rule-node-config.read-timeout-hint + + {{ 'rule-node-config.int-range' | translate }} + + + + rule-node-config.max-parallel-requests-count + + rule-node-config.max-parallel-requests-count-hint + + {{ 'rule-node-config.int-range' | translate }} + + + + rule-node-config.max-response-size + + rule-node-config.max-response-size-hint + + {{ 'rule-node-config.memory-buffer-size-range' | translate: { max: MemoryBufferSizeInKbLimit } }} + + + +
    + + + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/rest-api-call-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/external/rest-api-call-config.component.ts new file mode 100644 index 0000000000..fc6583c096 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/rest-api-call-config.component.ts @@ -0,0 +1,95 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@app/shared/models/rule-node.models'; +import { HttpRequestType, IntLimit } from '../rule-node-config.models'; + +@Component({ + selector: 'tb-external-node-rest-api-call-config', + templateUrl: './rest-api-call-config.component.html', + styleUrls: [] +}) +export class RestApiCallConfigComponent extends RuleNodeConfigurationComponent { + + restApiCallConfigForm: UntypedFormGroup; + + readonly proxySchemes: string[] = ['http', 'https']; + readonly httpRequestTypes = Object.keys(HttpRequestType); + readonly MemoryBufferSizeInKbLimit = 25000; + readonly IntLimit = IntLimit; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.restApiCallConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.restApiCallConfigForm = this.fb.group({ + restEndpointUrlPattern: [configuration ? configuration.restEndpointUrlPattern : null, [Validators.required]], + requestMethod: [configuration ? configuration.requestMethod : null, [Validators.required]], + useSimpleClientHttpFactory: [configuration ? configuration.useSimpleClientHttpFactory : false, []], + parseToPlainText: [configuration ? configuration.parseToPlainText : false, []], + ignoreRequestBody: [configuration ? configuration.ignoreRequestBody : false, []], + enableProxy: [configuration ? configuration.enableProxy : false, []], + useSystemProxyProperties: [configuration ? configuration.enableProxy : false, []], + proxyScheme: [configuration ? configuration.proxyHost : null, []], + proxyHost: [configuration ? configuration.proxyHost : null, []], + proxyPort: [configuration ? configuration.proxyPort : null, []], + proxyUser: [configuration ? configuration.proxyUser :null, []], + proxyPassword: [configuration ? configuration.proxyPassword :null, []], + readTimeoutMs: [configuration ? configuration.readTimeoutMs : null, [Validators.min(0), Validators.max(IntLimit)]], + maxParallelRequestsCount: [configuration ? configuration.maxParallelRequestsCount : null, [Validators.min(0), Validators.max(IntLimit)]], + headers: [configuration ? configuration.headers : null, []], + credentials: [configuration ? configuration.credentials : null, []], + maxInMemoryBufferSizeInKb: [configuration ? configuration.maxInMemoryBufferSizeInKb : null, [Validators.min(1), Validators.max(this.MemoryBufferSizeInKbLimit)]] + }); + } + + protected validatorTriggers(): string[] { + return ['useSimpleClientHttpFactory', 'enableProxy', 'useSystemProxyProperties']; + } + + protected updateValidators(emitEvent: boolean) { + const useSimpleClientHttpFactory: boolean = this.restApiCallConfigForm.get('useSimpleClientHttpFactory').value; + const enableProxy: boolean = this.restApiCallConfigForm.get('enableProxy').value; + const useSystemProxyProperties: boolean = this.restApiCallConfigForm.get('useSystemProxyProperties').value; + + if (enableProxy && !useSystemProxyProperties) { + this.restApiCallConfigForm.get('proxyHost').setValidators(enableProxy ? [Validators.required] : []); + this.restApiCallConfigForm.get('proxyPort').setValidators(enableProxy ? + [Validators.required, Validators.min(1), Validators.max(65535)] : []); + } else { + this.restApiCallConfigForm.get('proxyHost').setValidators([]); + this.restApiCallConfigForm.get('proxyPort').setValidators([]); + + if (useSimpleClientHttpFactory) { + this.restApiCallConfigForm.get('readTimeoutMs').setValidators([]); + } else { + this.restApiCallConfigForm.get('readTimeoutMs').setValidators([Validators.min(0), Validators.max(IntLimit)]); + } + } + + this.restApiCallConfigForm.get('readTimeoutMs').updateValueAndValidity({emitEvent}); + this.restApiCallConfigForm.get('proxyHost').updateValueAndValidity({emitEvent}); + this.restApiCallConfigForm.get('proxyPort').updateValueAndValidity({emitEvent}); + this.restApiCallConfigForm.get('credentials').updateValueAndValidity({emitEvent}); + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/send-email-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/external/send-email-config.component.html new file mode 100644 index 0000000000..f24c20038b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/send-email-config.component.html @@ -0,0 +1,116 @@ + +
    + + {{ 'rule-node-config.use-system-smtp-settings' | translate }} + +
    + + rule-node-config.smtp-protocol + + + {{ smtpProtocol.toUpperCase() }} + + + +
    + + rule-node-config.smtp-host + + + {{ 'rule-node-config.smtp-host-required' | translate }} + + + + rule-node-config.smtp-port + + + {{ 'rule-node-config.smtp-port-required' | translate }} + + + {{ 'rule-node-config.smtp-port-range' | translate }} + + + {{ 'rule-node-config.smtp-port-range' | translate }} + + +
    + + rule-node-config.timeout-msec + + + {{ 'rule-node-config.timeout-required' | translate }} + + + {{ 'rule-node-config.min-timeout-msec-message' | translate }} + + + + {{ 'rule-node-config.enable-tls' | translate }} + + + rule-node-config.tls-version + + + {{ tlsVersion }} + + + + + {{ 'rule-node-config.enable-proxy' | translate }} + +
    +
    + + rule-node-config.proxy-host + + + {{ 'rule-node-config.proxy-host-required' | translate }} + + + + rule-node-config.proxy-port + + + {{ 'rule-node-config.proxy-port-required' | translate }} + + + {{ 'rule-node-config.proxy-port-range' | translate }} + + +
    + + rule-node-config.proxy-user + + + + rule-node-config.proxy-password + + +
    + + rule-node-config.username + + + + rule-node-config.password + + + +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/send-email-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/external/send-email-config.component.ts new file mode 100644 index 0000000000..b0e77d6649 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/send-email-config.component.ts @@ -0,0 +1,95 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; + +@Component({ + selector: 'tb-external-node-send-email-config', + templateUrl: './send-email-config.component.html', + styleUrls: [] +}) +export class SendEmailConfigComponent extends RuleNodeConfigurationComponent { + + sendEmailConfigForm: UntypedFormGroup; + + smtpProtocols: string[] = [ + 'smtp', + 'smtps' + ]; + + tlsVersions = ['TLSv1', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3']; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.sendEmailConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.sendEmailConfigForm = this.fb.group({ + useSystemSmtpSettings: [configuration ? configuration.useSystemSmtpSettings : false, []], + smtpProtocol: [configuration ? configuration.smtpProtocol : null, []], + smtpHost: [configuration ? configuration.smtpHost : null, []], + smtpPort: [configuration ? configuration.smtpPort : null, []], + timeout: [configuration ? configuration.timeout : null, []], + enableTls: [configuration ? configuration.enableTls : false, []], + tlsVersion: [configuration ? configuration.tlsVersion : null, []], + enableProxy: [configuration ? configuration.enableProxy : false, []], + proxyHost: [configuration ? configuration.proxyHost : null, []], + proxyPort: [configuration ? configuration.proxyPort : null, []], + proxyUser: [configuration ? configuration.proxyUser :null, []], + proxyPassword: [configuration ? configuration.proxyPassword :null, []], + username: [configuration ? configuration.username : null, []], + password: [configuration ? configuration.password : null, []] + }); + } + + protected validatorTriggers(): string[] { + return ['useSystemSmtpSettings', 'enableProxy']; + } + + protected updateValidators(emitEvent: boolean) { + const useSystemSmtpSettings: boolean = this.sendEmailConfigForm.get('useSystemSmtpSettings').value; + const enableProxy: boolean = this.sendEmailConfigForm.get('enableProxy').value; + if (useSystemSmtpSettings) { + this.sendEmailConfigForm.get('smtpProtocol').setValidators([]); + this.sendEmailConfigForm.get('smtpHost').setValidators([]); + this.sendEmailConfigForm.get('smtpPort').setValidators([]); + this.sendEmailConfigForm.get('timeout').setValidators([]); + this.sendEmailConfigForm.get('proxyHost').setValidators([]); + this.sendEmailConfigForm.get('proxyPort').setValidators([]); + } else { + this.sendEmailConfigForm.get('smtpProtocol').setValidators([Validators.required]); + this.sendEmailConfigForm.get('smtpHost').setValidators([Validators.required]); + this.sendEmailConfigForm.get('smtpPort').setValidators([Validators.required, Validators.min(1), Validators.max(65535)]); + this.sendEmailConfigForm.get('timeout').setValidators([Validators.required, Validators.min(0)]); + this.sendEmailConfigForm.get('proxyHost').setValidators(enableProxy ? [Validators.required] : []); + this.sendEmailConfigForm.get('proxyPort').setValidators(enableProxy ? + [Validators.required, Validators.min(1), Validators.max(65535)] : []); + } + this.sendEmailConfigForm.get('smtpProtocol').updateValueAndValidity({emitEvent}); + this.sendEmailConfigForm.get('smtpHost').updateValueAndValidity({emitEvent}); + this.sendEmailConfigForm.get('smtpPort').updateValueAndValidity({emitEvent}); + this.sendEmailConfigForm.get('timeout').updateValueAndValidity({emitEvent}); + this.sendEmailConfigForm.get('proxyHost').updateValueAndValidity({emitEvent}); + this.sendEmailConfigForm.get('proxyPort').updateValueAndValidity({emitEvent}); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/send-sms-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/external/send-sms-config.component.html new file mode 100644 index 0000000000..3b4e63308a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/send-sms-config.component.html @@ -0,0 +1,43 @@ + +
    + + rule-node-config.numbers-to-template + + + {{ 'rule-node-config.numbers-to-template-required' | translate }} + + + + + rule-node-config.sms-message-template + + + {{ 'rule-node-config.sms-message-template-required' | translate }} + + rule-node-config.general-pattern-hint + + + {{ 'rule-node-config.use-system-sms-settings' | translate }} + + + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/send-sms-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/external/send-sms-config.component.ts new file mode 100644 index 0000000000..aef2077e1c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/send-sms-config.component.ts @@ -0,0 +1,61 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; + +@Component({ + selector: 'tb-external-node-send-sms-config', + templateUrl: './send-sms-config.component.html', + styleUrls: [] +}) +export class SendSmsConfigComponent extends RuleNodeConfigurationComponent { + + sendSmsConfigForm: UntypedFormGroup; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.sendSmsConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.sendSmsConfigForm = this.fb.group({ + numbersToTemplate: [configuration ? configuration.numbersToTemplate : null, [Validators.required]], + smsMessageTemplate: [configuration ? configuration.smsMessageTemplate : null, [Validators.required]], + useSystemSmsSettings: [configuration ? configuration.useSystemSmsSettings : false, []], + smsProviderConfiguration: [configuration ? configuration.smsProviderConfiguration : null, []], + }); + } + + protected validatorTriggers(): string[] { + return ['useSystemSmsSettings']; + } + + protected updateValidators(emitEvent: boolean) { + const useSystemSmsSettings: boolean = this.sendSmsConfigForm.get('useSystemSmsSettings').value; + if (useSystemSmsSettings) { + this.sendSmsConfigForm.get('smsProviderConfiguration').setValidators([]); + } else { + this.sendSmsConfigForm.get('smsProviderConfiguration').setValidators([Validators.required]); + } + this.sendSmsConfigForm.get('smsProviderConfiguration').updateValueAndValidity({emitEvent}); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/slack-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/external/slack-config.component.html new file mode 100644 index 0000000000..c5cf2b9d2f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/slack-config.component.html @@ -0,0 +1,49 @@ + +
    + + rule-node-config.message-template + + + {{ 'rule-node-config.message-template-required' | translate }} + + rule-node-config.general-pattern-hint + + + {{ 'rule-node-config.use-system-slack-settings' | translate }} + + + rule-node-config.slack-api-token + + + {{ 'rule-node-config.slack-api-token-required' | translate }} + + + + + + {{ slackChanelTypesTranslateMap.get(slackChanelType) | translate }} + + + + +
    diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructorFactory.java b/ui-ngx/src/app/modules/home/components/rule-node/external/slack-config.component.scss similarity index 57% rename from application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructorFactory.java rename to ui-ngx/src/app/modules/home/components/rule-node/external/slack-config.component.scss index b2d1e789d9..524b588874 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructorFactory.java +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/slack-config.component.scss @@ -13,14 +13,31 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.edge.rpc.constructor.dashboard; +:host { + .tb-title { + display: block; + padding-bottom: 6px; + } +} -import org.springframework.stereotype.Component; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.constructor.BaseMsgConstructorFactory; +:host ::ng-deep { + .mat-mdc-radio-group { + display: flex; + flex-direction: row; + margin-bottom: 22px; + gap: 12px; -@Component -@TbCoreComponent -public class DashboardMsgConstructorFactory extends BaseMsgConstructorFactory { + .mat-mdc-radio-button { + flex: 1 1 100%; + padding: 4px; + border: 1px solid rgba(0, 0, 0, 0.12); + border-radius: 6px; + } + } + @media screen and (max-width: 599px) { + .mat-mdc-radio-group { + flex-direction: column; + } + } } diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/slack-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/external/slack-config.component.ts new file mode 100644 index 0000000000..4d8cfc7a32 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/slack-config.component.ts @@ -0,0 +1,64 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, SlackChanelType, SlackChanelTypesTranslateMap } from '@app/shared/public-api'; +import { RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; + +@Component({ + selector: 'tb-external-node-slack-config', + templateUrl: './slack-config.component.html', + styleUrls: ['./slack-config.component.scss'] +}) +export class SlackConfigComponent extends RuleNodeConfigurationComponent { + + slackConfigForm: FormGroup; + slackChanelTypes = Object.keys(SlackChanelType) as SlackChanelType[]; + slackChanelTypesTranslateMap = SlackChanelTypesTranslateMap; + + constructor(private fb: FormBuilder) { + super(); + } + + protected configForm(): FormGroup { + return this.slackConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.slackConfigForm = this.fb.group({ + botToken: [configuration ? configuration.botToken : null], + useSystemSettings: [configuration ? configuration.useSystemSettings : false], + messageTemplate: [configuration ? configuration.messageTemplate : null, [Validators.required]], + conversationType: [configuration ? configuration.conversationType : null, [Validators.required]], + conversation: [configuration ? configuration.conversation : null, [Validators.required]], + }); + } + + protected validatorTriggers(): string[] { + return ['useSystemSettings']; + } + + protected updateValidators(emitEvent: boolean) { + const useSystemSettings: boolean = this.slackConfigForm.get('useSystemSettings').value; + if (useSystemSettings) { + this.slackConfigForm.get('botToken').clearValidators(); + } else { + this.slackConfigForm.get('botToken').setValidators([Validators.required]); + } + this.slackConfigForm.get('botToken').updateValueAndValidity({emitEvent}); + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/sns-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/external/sns-config.component.html new file mode 100644 index 0000000000..a5be9017a3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/sns-config.component.html @@ -0,0 +1,48 @@ + +
    + + rule-node-config.topic-arn-pattern + + + {{ 'rule-node-config.topic-arn-pattern-required' | translate }} + + rule-node-config.general-pattern-hint + + + rule-node-config.aws-access-key-id + + + {{ 'rule-node-config.aws-access-key-id-required' | translate }} + + + + rule-node-config.aws-secret-access-key + + + {{ 'rule-node-config.aws-secret-access-key-required' | translate }} + + + + rule-node-config.aws-region + + + {{ 'rule-node-config.aws-region-required' | translate }} + + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/sns-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/external/sns-config.component.ts new file mode 100644 index 0000000000..9c2159c2a9 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/sns-config.component.ts @@ -0,0 +1,46 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; + +@Component({ + selector: 'tb-external-node-sns-config', + templateUrl: './sns-config.component.html', + styleUrls: [] +}) +export class SnsConfigComponent extends RuleNodeConfigurationComponent { + + snsConfigForm: UntypedFormGroup; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.snsConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.snsConfigForm = this.fb.group({ + topicArnPattern: [configuration ? configuration.topicArnPattern : null, [Validators.required]], + accessKeyId: [configuration ? configuration.accessKeyId : null, [Validators.required]], + secretAccessKey: [configuration ? configuration.secretAccessKey : null, [Validators.required]], + region: [configuration ? configuration.region : null, [Validators.required]] + }); + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/sqs-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/external/sqs-config.component.html new file mode 100644 index 0000000000..2956cc4ab0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/sqs-config.component.html @@ -0,0 +1,76 @@ + +
    + + rule-node-config.queue-type + + + {{ sqsQueueTypeTranslationsMap.get(type) | translate }} + + + + + rule-node-config.queue-url-pattern + + + {{ 'rule-node-config.queue-url-pattern-required' | translate }} + + rule-node-config.general-pattern-hint + + + rule-node-config.delay-seconds + + + {{ 'rule-node-config.min-delay-seconds-message' | translate }} + + + {{ 'rule-node-config.max-delay-seconds-message' | translate }} + + + +
    + + + + rule-node-config.aws-access-key-id + + + {{ 'rule-node-config.aws-access-key-id-required' | translate }} + + + + rule-node-config.aws-secret-access-key + + + {{ 'rule-node-config.aws-secret-access-key-required' | translate }} + + + + rule-node-config.aws-region + + + {{ 'rule-node-config.aws-region-required' | translate }} + + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/sqs-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/external/sqs-config.component.ts new file mode 100644 index 0000000000..8e77682c4f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/sqs-config.component.ts @@ -0,0 +1,54 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@app/shared/models/rule-node.models'; +import { SqsQueueType, sqsQueueTypeTranslations } from '@home/components/rule-node/rule-node-config.models'; + +@Component({ + selector: 'tb-external-node-sqs-config', + templateUrl: './sqs-config.component.html', + styleUrls: [] +}) +export class SqsConfigComponent extends RuleNodeConfigurationComponent { + + sqsConfigForm: UntypedFormGroup; + + sqsQueueType = SqsQueueType; + sqsQueueTypes = Object.keys(SqsQueueType); + sqsQueueTypeTranslationsMap = sqsQueueTypeTranslations; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.sqsConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.sqsConfigForm = this.fb.group({ + queueType: [configuration ? configuration.queueType : null, [Validators.required]], + queueUrlPattern: [configuration ? configuration.queueUrlPattern : null, [Validators.required]], + delaySeconds: [configuration ? configuration.delaySeconds : null, [Validators.min(0), Validators.max(900)]], + messageAttributes: [configuration ? configuration.messageAttributes : null, []], + accessKeyId: [configuration ? configuration.accessKeyId : null, [Validators.required]], + secretAccessKey: [configuration ? configuration.secretAccessKey : null, [Validators.required]], + region: [configuration ? configuration.region : null, [Validators.required]] + }); + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/filter/check-alarm-status.component.html b/ui-ngx/src/app/modules/home/components/rule-node/filter/check-alarm-status.component.html new file mode 100644 index 0000000000..b8f0faf15f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/filter/check-alarm-status.component.html @@ -0,0 +1,29 @@ + +
    +
    +
    rule-node-config.alarm-status
    +
    + rule-node-config.alarm-required +
    +
    + +
    + + + diff --git a/ui-ngx/src/app/modules/home/components/rule-node/filter/check-alarm-status.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/filter/check-alarm-status.component.ts new file mode 100644 index 0000000000..125fde1bd6 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/filter/check-alarm-status.component.ts @@ -0,0 +1,52 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { isDefinedAndNotNull } from '@core/public-api'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@app/shared/models/rule-node.models'; + +@Component({ + selector: 'tb-filter-node-check-alarm-status-config', + templateUrl: './check-alarm-status.component.html', + styleUrls: [] +}) +export class CheckAlarmStatusComponent extends RuleNodeConfigurationComponent { + alarmStatusConfigForm: FormGroup; + + searchText = ''; + + constructor(private fb: FormBuilder) { + super(); + } + + protected configForm(): FormGroup { + return this.alarmStatusConfigForm; + } + + protected prepareInputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + return { + alarmStatusList: isDefinedAndNotNull(configuration?.alarmStatusList) ? configuration.alarmStatusList : null + }; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.alarmStatusConfigForm = this.fb.group({ + alarmStatusList: [configuration.alarmStatusList, [Validators.required]], + }); + } +} + diff --git a/ui-ngx/src/app/modules/home/components/rule-node/filter/check-message-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/filter/check-message-config.component.html new file mode 100644 index 0000000000..6566912570 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/filter/check-message-config.component.html @@ -0,0 +1,45 @@ + +
    +
    +
    rule-node-config.fields-to-check
    +
    + rule-node-config.at-least-one-field-required +
    +
    + + help + + + help + +
    + + {{ 'rule-node-config.check-all-keys' | translate }} + +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/filter/check-message-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/filter/check-message-config.component.ts new file mode 100644 index 0000000000..2904c8eee2 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/filter/check-message-config.component.ts @@ -0,0 +1,78 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { isDefinedAndNotNull } from '@core/public-api'; +import { FormBuilder, FormGroup, ValidationErrors, ValidatorFn, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@app/shared/models/rule-node.models'; + +@Component({ + selector: 'tb-filter-node-check-message-config', + templateUrl: './check-message-config.component.html', + styleUrls: [] +}) +export class CheckMessageConfigComponent extends RuleNodeConfigurationComponent { + + checkMessageConfigForm: FormGroup; + + constructor(private fb: FormBuilder) { + super(); + } + + protected configForm(): FormGroup { + return this.checkMessageConfigForm; + } + + protected prepareInputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + return { + messageNames: isDefinedAndNotNull(configuration?.messageNames) ? configuration.messageNames : [], + metadataNames: isDefinedAndNotNull(configuration?.metadataNames) ? configuration.metadataNames : [], + checkAllKeys: isDefinedAndNotNull(configuration?.checkAllKeys) ? configuration.checkAllKeys : false + }; + } + + protected prepareOutputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + return { + messageNames: isDefinedAndNotNull(configuration?.messageNames) ? configuration.messageNames : [], + metadataNames: isDefinedAndNotNull(configuration?.metadataNames) ? configuration.metadataNames : [], + checkAllKeys: configuration.checkAllKeys + }; + } + + + private atLeastOne(validator: ValidatorFn, controls: string[] = null) { + return (group: FormGroup): ValidationErrors | null => { + if (!controls) { + controls = Object.keys(group.controls); + } + const hasAtLeastOne = group?.controls && controls.some(k => !validator(group.controls[k])); + + return hasAtLeastOne ? null : {atLeastOne: true}; + }; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.checkMessageConfigForm = this.fb.group({ + messageNames: [configuration.messageNames, []], + metadataNames: [configuration.metadataNames, []], + checkAllKeys: [configuration.checkAllKeys, []] + }, {validators: this.atLeastOne(Validators.required, ['messageNames', 'metadataNames'])}); + } + + get touchedValidationControl(): boolean { + return ['messageNames', 'metadataNames'].some(name => this.checkMessageConfigForm.get(name).touched); + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/filter/check-relation-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/filter/check-relation-config.component.html new file mode 100644 index 0000000000..b9763b280f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/filter/check-relation-config.component.html @@ -0,0 +1,55 @@ + +
    +
    rule-node-config.relation-search-parameters
    +
    + + {{ 'relation.direction' | translate }} + + + {{ entitySearchDirectionTranslationsMap.get(direction) | translate }} rule-node-config.relations-query-config-direction-suffix + + + + + +
    + + {{ 'rule-node-config.check-relation-to-specific-entity' | translate }} + +
    +
    + + + + +
    +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/filter/check-relation-config.component.scss b/ui-ngx/src/app/modules/home/components/rule-node/filter/check-relation-config.component.scss new file mode 100644 index 0000000000..b61d0a5bb5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/filter/check-relation-config.component.scss @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + .slide-toggle { + margin-bottom: 18px + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/filter/check-relation-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/filter/check-relation-config.component.ts new file mode 100644 index 0000000000..6c8a01050f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/filter/check-relation-config.component.ts @@ -0,0 +1,77 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { isDefinedAndNotNull } from '@core/public-api'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { EntitySearchDirection, entitySearchDirectionTranslations } from '@app/shared/models/relation.models'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@app/shared/models/rule-node.models'; + +@Component({ + selector: 'tb-filter-node-check-relation-config', + templateUrl: './check-relation-config.component.html', + styleUrls: ['./check-relation-config.component.scss'] +}) +export class CheckRelationConfigComponent extends RuleNodeConfigurationComponent { + + checkRelationConfigForm: UntypedFormGroup; + + entitySearchDirection: Array = Object.values(EntitySearchDirection); + entitySearchDirectionTranslationsMap = entitySearchDirectionTranslations; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.checkRelationConfigForm; + } + + protected prepareInputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + return { + checkForSingleEntity: isDefinedAndNotNull(configuration?.checkForSingleEntity) ? configuration.checkForSingleEntity : false, + direction: isDefinedAndNotNull(configuration?.direction) ? configuration.direction : null, + entityType: isDefinedAndNotNull(configuration?.entityType) ? configuration.entityType : null, + entityId: isDefinedAndNotNull(configuration?.entityId) ? configuration.entityId : null, + relationType: isDefinedAndNotNull(configuration?.relationType) ? configuration.relationType : null + }; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.checkRelationConfigForm = this.fb.group({ + checkForSingleEntity: [configuration.checkForSingleEntity, []], + direction: [configuration.direction, []], + entityType: [configuration.entityType, + configuration && configuration.checkForSingleEntity ? [Validators.required] : []], + entityId: [configuration.entityId, + configuration && configuration.checkForSingleEntity ? [Validators.required] : []], + relationType: [configuration.relationType, [Validators.required]] + }); + } + + protected validatorTriggers(): string[] { + return ['checkForSingleEntity']; + } + + protected updateValidators(emitEvent: boolean) { + const checkForSingleEntity: boolean = this.checkRelationConfigForm.get('checkForSingleEntity').value; + this.checkRelationConfigForm.get('entityType').setValidators(checkForSingleEntity ? [Validators.required] : []); + this.checkRelationConfigForm.get('entityType').updateValueAndValidity({emitEvent}); + this.checkRelationConfigForm.get('entityId').setValidators(checkForSingleEntity ? [Validators.required] : []); + this.checkRelationConfigForm.get('entityId').updateValueAndValidity({emitEvent}); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/filter/filter-rule-node-config.module.ts b/ui-ngx/src/app/modules/home/components/rule-node/filter/filter-rule-node-config.module.ts new file mode 100644 index 0000000000..2b26991a16 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/filter/filter-rule-node-config.module.ts @@ -0,0 +1,58 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@shared/public-api'; +import { CheckMessageConfigComponent } from './check-message-config.component'; +import { CheckRelationConfigComponent } from './check-relation-config.component'; +import { GpsGeoFilterConfigComponent } from './gps-geo-filter-config.component'; +import { MessageTypeConfigComponent } from './message-type-config.component'; +import { OriginatorTypeConfigComponent } from './originator-type-config.component'; +import { ScriptConfigComponent } from './script-config.component'; +import { SwitchConfigComponent } from './switch-config.component'; +import { CheckAlarmStatusComponent } from './check-alarm-status.component'; +import { CommonRuleNodeConfigModule } from '../common/common-rule-node-config.module'; + +@NgModule({ + declarations: [ + CheckMessageConfigComponent, + CheckRelationConfigComponent, + GpsGeoFilterConfigComponent, + MessageTypeConfigComponent, + OriginatorTypeConfigComponent, + ScriptConfigComponent, + SwitchConfigComponent, + CheckAlarmStatusComponent + ], + imports: [ + CommonModule, + SharedModule, + CommonRuleNodeConfigModule + ], + exports: [ + CheckMessageConfigComponent, + CheckRelationConfigComponent, + GpsGeoFilterConfigComponent, + MessageTypeConfigComponent, + OriginatorTypeConfigComponent, + ScriptConfigComponent, + SwitchConfigComponent, + CheckAlarmStatusComponent + ] +}) +export class FilterRuleNodeConfigModule { +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/filter/gps-geo-filter-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/filter/gps-geo-filter-config.component.html new file mode 100644 index 0000000000..ac09e2156a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/filter/gps-geo-filter-config.component.html @@ -0,0 +1,126 @@ + +
    +
    +
    rule-node-config.coordinate-field-names
    +
    +
    + + {{ 'rule-node-config.latitude-field-name' | translate }} + + + {{ 'rule-node-config.latitude-field-name-required' | translate }} + + + + {{ 'rule-node-config.longitude-field-name' | translate }} + + + {{ 'rule-node-config.longitude-field-name-required' | translate }} + + +
    +
    rule-node-config.coordinate-field-hint
    +
    +
    +
    +
    rule-node-config.geofence-configuration
    +
    + + {{ 'rule-node-config.perimeter-type' | translate }} + + + {{ perimeterTypeTranslationMap.get(type) | translate }} + + + +
    + + {{ 'rule-node-config.fetch-perimeter-info-from-metadata' | translate }} + +
    + + {{ 'rule-node-config.perimeter-key-name' | translate }} + + + {{ 'rule-node-config.perimeter-key-name-required' | translate }} + + {{ 'rule-node-config.perimeter-key-name-hint' | translate }} + +
    +
    + + {{ 'rule-node-config.circle-center-latitude' | translate }} + + + {{ 'rule-node-config.circle-center-latitude-required' | translate }} + + + + {{ 'rule-node-config.circle-center-longitude' | translate }} + + + {{ 'rule-node-config.circle-center-longitude-required' | translate }} + + +
    +
    + + {{ 'rule-node-config.range' | translate }} + + + {{ 'rule-node-config.range-required' | translate }} + + + + {{ 'rule-node-config.range-units' | translate }} + + + {{ rangeUnitTranslationMap.get(type) | translate }} + + + + {{ 'rule-node-config.range-units-required' | translate }} + + +
    +
    + + {{ 'rule-node-config.polygon-definition' | translate }} + + {{ 'rule-node-config.polygon-definition-hint' | translate }} + + {{ 'rule-node-config.polygon-definition-required' | translate }} + + +
    +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/filter/gps-geo-filter-config.component.scss b/ui-ngx/src/app/modules/home/components/rule-node/filter/gps-geo-filter-config.component.scss new file mode 100644 index 0000000000..f177555e37 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/filter/gps-geo-filter-config.component.scss @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + .slide-toggle { + margin-bottom: 18px; + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/filter/gps-geo-filter-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/filter/gps-geo-filter-config.component.ts new file mode 100644 index 0000000000..6fb2158e7a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/filter/gps-geo-filter-config.component.ts @@ -0,0 +1,121 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { isDefinedAndNotNull } from '@core/public-api'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@app/shared/models/rule-node.models'; +import { PerimeterType, perimeterTypeTranslations, RangeUnit, rangeUnitTranslations } from '../rule-node-config.models'; + +@Component({ + selector: 'tb-filter-node-gps-geofencing-config', + templateUrl: './gps-geo-filter-config.component.html', + styleUrls: ['./gps-geo-filter-config.component.scss'] +}) +export class GpsGeoFilterConfigComponent extends RuleNodeConfigurationComponent { + + geoFilterConfigForm: FormGroup; + + perimeterType = PerimeterType; + perimeterTypes: Array = Object.values(PerimeterType); + perimeterTypeTranslationMap = perimeterTypeTranslations; + + rangeUnits: Array = Object.values(RangeUnit); + rangeUnitTranslationMap = rangeUnitTranslations; + + public defaultPaddingEnable = true; + + constructor(private fb: FormBuilder) { + super(); + } + + protected configForm(): FormGroup { + return this.geoFilterConfigForm; + } + + protected prepareInputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + return { + latitudeKeyName: isDefinedAndNotNull(configuration?.latitudeKeyName) ? configuration.latitudeKeyName : null, + longitudeKeyName: isDefinedAndNotNull(configuration?.longitudeKeyName) ? configuration.longitudeKeyName : null, + perimeterType: isDefinedAndNotNull(configuration?.perimeterType) ? configuration.perimeterType : null, + fetchPerimeterInfoFromMessageMetadata: isDefinedAndNotNull(configuration?.fetchPerimeterInfoFromMessageMetadata) ? + configuration.fetchPerimeterInfoFromMessageMetadata : false, + perimeterKeyName: isDefinedAndNotNull(configuration?.perimeterKeyName) ? configuration.perimeterKeyName : null, + centerLatitude: isDefinedAndNotNull(configuration?.centerLatitude) ? configuration.centerLatitude : null, + centerLongitude: isDefinedAndNotNull(configuration?.centerLongitude) ? configuration.centerLongitude : null, + range: isDefinedAndNotNull(configuration?.range) ? configuration.range : null, + rangeUnit: isDefinedAndNotNull(configuration?.rangeUnit) ? configuration.rangeUnit : null, + polygonsDefinition: isDefinedAndNotNull(configuration?.polygonsDefinition) ? configuration.polygonsDefinition : null + }; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.geoFilterConfigForm = this.fb.group({ + latitudeKeyName: [configuration.latitudeKeyName, [Validators.required]], + longitudeKeyName: [configuration.longitudeKeyName, [Validators.required]], + perimeterType: [configuration.perimeterType, [Validators.required]], + fetchPerimeterInfoFromMessageMetadata: [configuration.fetchPerimeterInfoFromMessageMetadata, []], + perimeterKeyName: [configuration.perimeterKeyName, []], + centerLatitude: [configuration.centerLatitude, []], + centerLongitude: [configuration.centerLongitude, []], + range: [configuration.range, []], + rangeUnit: [configuration.rangeUnit, []], + polygonsDefinition: [configuration.polygonsDefinition, []] + }); + } + + protected validatorTriggers(): string[] { + return ['fetchPerimeterInfoFromMessageMetadata', 'perimeterType']; + } + + protected updateValidators(emitEvent: boolean) { + const fetchPerimeterInfoFromMessageMetadata: boolean = this.geoFilterConfigForm.get('fetchPerimeterInfoFromMessageMetadata').value; + const perimeterType: PerimeterType = this.geoFilterConfigForm.get('perimeterType').value; + if (fetchPerimeterInfoFromMessageMetadata) { + this.geoFilterConfigForm.get('perimeterKeyName').setValidators([Validators.required]); + } else { + this.geoFilterConfigForm.get('perimeterKeyName').setValidators([]); + } + if (!fetchPerimeterInfoFromMessageMetadata && perimeterType === PerimeterType.CIRCLE) { + this.geoFilterConfigForm.get('centerLatitude').setValidators([Validators.required, + Validators.min(-90), Validators.max(90)]); + this.geoFilterConfigForm.get('centerLongitude').setValidators([Validators.required, + Validators.min(-180), Validators.max(180)]); + this.geoFilterConfigForm.get('range').setValidators([Validators.required, Validators.min(0)]); + this.geoFilterConfigForm.get('rangeUnit').setValidators([Validators.required]); + + this.defaultPaddingEnable = false; + } else { + this.geoFilterConfigForm.get('centerLatitude').setValidators([]); + this.geoFilterConfigForm.get('centerLongitude').setValidators([]); + this.geoFilterConfigForm.get('range').setValidators([]); + this.geoFilterConfigForm.get('rangeUnit').setValidators([]); + + this.defaultPaddingEnable = true; + } + if (!fetchPerimeterInfoFromMessageMetadata && perimeterType === PerimeterType.POLYGON) { + this.geoFilterConfigForm.get('polygonsDefinition').setValidators([Validators.required]); + } else { + this.geoFilterConfigForm.get('polygonsDefinition').setValidators([]); + } + this.geoFilterConfigForm.get('perimeterKeyName').updateValueAndValidity({emitEvent}); + this.geoFilterConfigForm.get('centerLatitude').updateValueAndValidity({emitEvent}); + this.geoFilterConfigForm.get('centerLongitude').updateValueAndValidity({emitEvent}); + this.geoFilterConfigForm.get('range').updateValueAndValidity({emitEvent}); + this.geoFilterConfigForm.get('rangeUnit').updateValueAndValidity({emitEvent}); + this.geoFilterConfigForm.get('polygonsDefinition').updateValueAndValidity({emitEvent}); + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/filter/message-type-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/filter/message-type-config.component.html new file mode 100644 index 0000000000..847ab255f5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/filter/message-type-config.component.html @@ -0,0 +1,24 @@ + +
    + + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/filter/message-type-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/filter/message-type-config.component.ts new file mode 100644 index 0000000000..61cc018aab --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/filter/message-type-config.component.ts @@ -0,0 +1,50 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { isDefinedAndNotNull } from '@core/public-api'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; + +@Component({ + selector: 'tb-filter-node-message-type-config', + templateUrl: './message-type-config.component.html', + styleUrls: [] +}) +export class MessageTypeConfigComponent extends RuleNodeConfigurationComponent { + + messageTypeConfigForm: UntypedFormGroup; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.messageTypeConfigForm; + } + + protected prepareInputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + return { + messageTypes: isDefinedAndNotNull(configuration?.messageTypes) ? configuration.messageTypes : null + }; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.messageTypeConfigForm = this.fb.group({ + messageTypes: [configuration.messageTypes, [Validators.required]] + }); + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/filter/originator-type-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/filter/originator-type-config.component.html new file mode 100644 index 0000000000..1dc3cf34a2 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/filter/originator-type-config.component.html @@ -0,0 +1,31 @@ + +
    + + help + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/filter/originator-type-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/filter/originator-type-config.component.ts new file mode 100644 index 0000000000..91e945ee0f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/filter/originator-type-config.component.ts @@ -0,0 +1,65 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { isDefinedAndNotNull } from '@core/public-api'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@app/shared/models/rule-node.models'; +import { EntityType } from '@app/shared/models/entity-type.models'; + +@Component({ + selector: 'tb-filter-node-originator-type-config', + templateUrl: './originator-type-config.component.html', + styleUrls: [] +}) +export class OriginatorTypeConfigComponent extends RuleNodeConfigurationComponent { + + originatorTypeConfigForm: UntypedFormGroup; + + allowedEntityTypes: EntityType[] = [ + EntityType.DEVICE, + EntityType.ASSET, + EntityType.ENTITY_VIEW, + EntityType.TENANT, + EntityType.CUSTOMER, + EntityType.USER, + EntityType.DASHBOARD, + EntityType.RULE_CHAIN, + EntityType.RULE_NODE, + EntityType.EDGE + ]; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.originatorTypeConfigForm; + } + + protected prepareInputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + return { + originatorTypes: isDefinedAndNotNull(configuration?.originatorTypes) ? configuration.originatorTypes : null + }; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.originatorTypeConfigForm = this.fb.group({ + originatorTypes: [configuration.originatorTypes, [Validators.required]] + }); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/filter/script-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/filter/script-config.component.html new file mode 100644 index 0000000000..c0b39f77b7 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/filter/script-config.component.html @@ -0,0 +1,57 @@ + +
    + + + + + + + + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/filter/script-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/filter/script-config.component.ts new file mode 100644 index 0000000000..a5e4ffdea6 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/filter/script-config.component.ts @@ -0,0 +1,128 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, EventEmitter, ViewChild } from '@angular/core'; +import { AppState, getCurrentAuthState, isDefinedAndNotNull, NodeScriptTestService } from '@core/public-api'; +import { Store } from '@ngrx/store'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent, ScriptLanguage } from '@shared/models/rule-node.models'; +import type { JsFuncComponent } from '@app/shared/components/js-func.component'; +import { DebugRuleNodeEventBody } from '@app/shared/models/event.models'; + +@Component({ + selector: 'tb-filter-node-script-config', + templateUrl: './script-config.component.html', + styleUrls: [] +}) +export class ScriptConfigComponent extends RuleNodeConfigurationComponent { + + @ViewChild('jsFuncComponent', {static: false}) jsFuncComponent: JsFuncComponent; + @ViewChild('tbelFuncComponent', {static: false}) tbelFuncComponent: JsFuncComponent; + + scriptConfigForm: UntypedFormGroup; + + tbelEnabled = getCurrentAuthState(this.store).tbelEnabled; + + scriptLanguage = ScriptLanguage; + + changeScript: EventEmitter = new EventEmitter(); + + readonly hasScript = true; + + readonly testScriptLabel = 'rule-node-config.test-filter-function'; + + constructor(protected store: Store, + private fb: UntypedFormBuilder, + private nodeScriptTestService: NodeScriptTestService, + private translate: TranslateService) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.scriptConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.scriptConfigForm = this.fb.group({ + scriptLang: [configuration.scriptLang, [Validators.required]], + jsScript: [configuration.jsScript, []], + tbelScript: [configuration.tbelScript, []] + }); + } + + protected validatorTriggers(): string[] { + return ['scriptLang']; + } + + protected updateValidators(emitEvent: boolean) { + let scriptLang: ScriptLanguage = this.scriptConfigForm.get('scriptLang').value; + if (scriptLang === ScriptLanguage.TBEL && !this.tbelEnabled) { + scriptLang = ScriptLanguage.JS; + this.scriptConfigForm.get('scriptLang').patchValue(scriptLang, {emitEvent: false}); + setTimeout(() => { + this.scriptConfigForm.updateValueAndValidity({emitEvent: true}); + }); + } + this.scriptConfigForm.get('jsScript').setValidators(scriptLang === ScriptLanguage.JS ? [Validators.required] : []); + this.scriptConfigForm.get('jsScript').updateValueAndValidity({emitEvent}); + this.scriptConfigForm.get('tbelScript').setValidators(scriptLang === ScriptLanguage.TBEL ? [Validators.required] : []); + this.scriptConfigForm.get('tbelScript').updateValueAndValidity({emitEvent}); + } + + protected prepareInputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + if (configuration) { + if (!configuration.scriptLang) { + configuration.scriptLang = ScriptLanguage.JS; + } + } + return { + scriptLang: isDefinedAndNotNull(configuration?.scriptLang) ? configuration.scriptLang : ScriptLanguage.JS, + jsScript: isDefinedAndNotNull(configuration?.jsScript) ? configuration.jsScript : null, + tbelScript: isDefinedAndNotNull(configuration?.tbelScript) ? configuration.tbelScript : null + }; + } + + testScript(debugEventBody?: DebugRuleNodeEventBody) { + const scriptLang: ScriptLanguage = this.scriptConfigForm.get('scriptLang').value; + const scriptField = scriptLang === ScriptLanguage.JS ? 'jsScript' : 'tbelScript'; + const helpId = scriptLang === ScriptLanguage.JS ? 'rulenode/filter_node_script_fn' : 'rulenode/tbel/filter_node_script_fn'; + const script: string = this.scriptConfigForm.get(scriptField).value; + this.nodeScriptTestService.testNodeScript( + script, + 'filter', + this.translate.instant('rule-node-config.filter'), + 'Filter', + ['msg', 'metadata', 'msgType'], + this.ruleNodeId, + helpId, + scriptLang, + debugEventBody + ).subscribe((theScript) => { + if (theScript) { + this.scriptConfigForm.get(scriptField).setValue(theScript); + this.changeScript.emit(); + } + }); + } + + protected onValidate() { + const scriptLang: ScriptLanguage = this.scriptConfigForm.get('scriptLang').value; + if (scriptLang === ScriptLanguage.JS) { + this.jsFuncComponent.validateOnSubmit(); + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/filter/switch-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/filter/switch-config.component.html new file mode 100644 index 0000000000..83034e2e43 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/filter/switch-config.component.html @@ -0,0 +1,57 @@ + +
    + + + + + + + + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/filter/switch-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/filter/switch-config.component.ts new file mode 100644 index 0000000000..c444effd08 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/filter/switch-config.component.ts @@ -0,0 +1,130 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, EventEmitter, ViewChild } from '@angular/core'; +import { getCurrentAuthState, isDefinedAndNotNull, NodeScriptTestService } from '@core/public-api'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; +import { + RuleNodeConfiguration, + RuleNodeConfigurationComponent, + ScriptLanguage +} from '@app/shared/models/rule-node.models'; +import type { JsFuncComponent } from '@app/shared/components/js-func.component'; +import { DebugRuleNodeEventBody } from '@shared/models/event.models'; + +@Component({ + selector: 'tb-filter-node-switch-config', + templateUrl: './switch-config.component.html', + styleUrls: [] +}) +export class SwitchConfigComponent extends RuleNodeConfigurationComponent { + + @ViewChild('jsFuncComponent', {static: false}) jsFuncComponent: JsFuncComponent; + @ViewChild('tbelFuncComponent', {static: false}) tbelFuncComponent: JsFuncComponent; + + switchConfigForm: UntypedFormGroup; + + tbelEnabled = getCurrentAuthState(this.store).tbelEnabled; + + scriptLanguage = ScriptLanguage; + + changeScript: EventEmitter = new EventEmitter(); + + readonly hasScript = true; + + readonly testScriptLabel = 'rule-node-config.test-switch-function'; + + constructor(private fb: UntypedFormBuilder, + private nodeScriptTestService: NodeScriptTestService, + private translate: TranslateService) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.switchConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.switchConfigForm = this.fb.group({ + scriptLang: [configuration.scriptLang, [Validators.required]], + jsScript: [configuration.jsScript, []], + tbelScript: [configuration.tbelScript, []] + }); + } + + protected validatorTriggers(): string[] { + return ['scriptLang']; + } + + protected updateValidators(emitEvent: boolean) { + let scriptLang: ScriptLanguage = this.switchConfigForm.get('scriptLang').value; + if (scriptLang === ScriptLanguage.TBEL && !this.tbelEnabled) { + scriptLang = ScriptLanguage.JS; + this.switchConfigForm.get('scriptLang').patchValue(scriptLang, {emitEvent: false}); + setTimeout(() => { + this.switchConfigForm.updateValueAndValidity({emitEvent: true}); + }); + } + this.switchConfigForm.get('jsScript').setValidators(scriptLang === ScriptLanguage.JS ? [Validators.required] : []); + this.switchConfigForm.get('jsScript').updateValueAndValidity({emitEvent}); + this.switchConfigForm.get('tbelScript').setValidators(scriptLang === ScriptLanguage.TBEL ? [Validators.required] : []); + this.switchConfigForm.get('tbelScript').updateValueAndValidity({emitEvent}); + } + + protected prepareInputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + if (configuration) { + if (!configuration.scriptLang) { + configuration.scriptLang = ScriptLanguage.JS; + } + } + return { + scriptLang: isDefinedAndNotNull(configuration?.scriptLang) ? configuration.scriptLang : ScriptLanguage.JS, + jsScript: isDefinedAndNotNull(configuration?.jsScript) ? configuration.jsScript : null, + tbelScript: isDefinedAndNotNull(configuration?.tbelScript) ? configuration.tbelScript : null + }; + } + + testScript(debugEventBody?: DebugRuleNodeEventBody) { + const scriptLang: ScriptLanguage = this.switchConfigForm.get('scriptLang').value; + const scriptField = scriptLang === ScriptLanguage.JS ? 'jsScript' : 'tbelScript'; + const helpId = scriptLang === ScriptLanguage.JS ? 'rulenode/switch_node_script_fn' : 'rulenode/tbel/switch_node_script_fn'; + const script: string = this.switchConfigForm.get(scriptField).value; + this.nodeScriptTestService.testNodeScript( + script, + 'switch', + this.translate.instant('rule-node-config.switch'), + 'Switch', + ['msg', 'metadata', 'msgType'], + this.ruleNodeId, + helpId, + scriptLang, + debugEventBody + ).subscribe((theScript) => { + if (theScript) { + this.switchConfigForm.get(scriptField).setValue(theScript); + this.changeScript.emit(); + } + }); + } + + protected onValidate() { + const scriptLang: ScriptLanguage = this.switchConfigForm.get('scriptLang').value; + if (scriptLang === ScriptLanguage.JS) { + this.jsFuncComponent.validateOnSubmit(); + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/flow/flow-rule-node-config.module.ts b/ui-ngx/src/app/modules/home/components/rule-node/flow/flow-rule-node-config.module.ts new file mode 100644 index 0000000000..95668cf19e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/flow/flow-rule-node-config.module.ts @@ -0,0 +1,38 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@shared/public-api'; +import { RuleChainInputComponent } from './rule-chain-input.component'; +import { RuleChainOutputComponent } from './rule-chain-output.component'; + +@NgModule({ + declarations: [ + RuleChainInputComponent, + RuleChainOutputComponent + ], + imports: [ + CommonModule, + SharedModule + ], + exports: [ + RuleChainInputComponent, + RuleChainOutputComponent + ] +}) +export class FlowRuleNodeConfigModule { +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/flow/rule-chain-input.component.html b/ui-ngx/src/app/modules/home/components/rule-node/flow/rule-chain-input.component.html new file mode 100644 index 0000000000..6bee899138 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/flow/rule-chain-input.component.html @@ -0,0 +1,33 @@ + +
    +
    +
    + + {{ 'rule-node-config.forward-msg-default-rule-chain' | translate }} + +
    + + +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/flow/rule-chain-input.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/flow/rule-chain-input.component.ts new file mode 100644 index 0000000000..9e5316841a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/flow/rule-chain-input.component.ts @@ -0,0 +1,48 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; +import { EntityType } from '@shared/models/entity-type.models'; + +@Component({ + selector: 'tb-flow-node-rule-chain-input-config', + templateUrl: './rule-chain-input.component.html', + styleUrls: [] +}) +export class RuleChainInputComponent extends RuleNodeConfigurationComponent { + + entityType = EntityType; + + ruleChainInputConfigForm: UntypedFormGroup; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.ruleChainInputConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.ruleChainInputConfigForm = this.fb.group({ + forwardMsgToDefaultRuleChain: [configuration ? configuration?.forwardMsgToDefaultRuleChain : false, []], + ruleChainId: [configuration ? configuration.ruleChainId : null, [Validators.required]] + }); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/flow/rule-chain-output.component.html b/ui-ngx/src/app/modules/home/components/rule-node/flow/rule-chain-output.component.html new file mode 100644 index 0000000000..8fafa22061 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/flow/rule-chain-output.component.html @@ -0,0 +1,20 @@ + +
    +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/flow/rule-chain-output.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/flow/rule-chain-output.component.ts new file mode 100644 index 0000000000..153272f374 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/flow/rule-chain-output.component.ts @@ -0,0 +1,42 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; + +@Component({ + selector: 'tb-flow-node-rule-chain-output-config', + templateUrl: './rule-chain-output.component.html', + styleUrls: [] +}) +export class RuleChainOutputComponent extends RuleNodeConfigurationComponent { + + ruleChainOutputConfigForm: UntypedFormGroup; + + constructor(private fb: UntypedFormBuilder) { + super(); + } + + protected configForm(): UntypedFormGroup { + return this.ruleChainOutputConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.ruleChainOutputConfigForm = this.fb.group({}); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/rule-node-config.models.ts b/ui-ngx/src/app/modules/home/components/rule-node/rule-node-config.models.ts new file mode 100644 index 0000000000..a358bbaf12 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/rule-node-config.models.ts @@ -0,0 +1,862 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { EntityField, entityFields } from '@shared/models/entity.models'; +import { EntitySearchDirection } from '@shared/models/relation.models'; +import { EntityTypeFilter } from '@shared/models/alias.models'; + +export enum OriginatorSource { + CUSTOMER = 'CUSTOMER', + TENANT = 'TENANT', + RELATED = 'RELATED', + ALARM_ORIGINATOR = 'ALARM_ORIGINATOR', + ENTITY = 'ENTITY' +} + +export interface OriginatorValuesDescriptions { + value: OriginatorSource; + name: string; + description: string; +} + +export const originatorSourceTranslations = new Map( + [ + [OriginatorSource.CUSTOMER, 'rule-node-config.originator-customer'], + [OriginatorSource.TENANT, 'rule-node-config.originator-tenant'], + [OriginatorSource.RELATED, 'rule-node-config.originator-related'], + [OriginatorSource.ALARM_ORIGINATOR, 'rule-node-config.originator-alarm-originator'], + [OriginatorSource.ENTITY, 'rule-node-config.originator-entity'], + ] +); + +export const originatorSourceDescTranslations = new Map( + [ + [OriginatorSource.CUSTOMER, 'rule-node-config.originator-customer-desc'], + [OriginatorSource.TENANT, 'rule-node-config.originator-tenant-desc'], + [OriginatorSource.RELATED, 'rule-node-config.originator-related-entity-desc'], + [OriginatorSource.ALARM_ORIGINATOR, 'rule-node-config.originator-alarm-originator-desc'], + [OriginatorSource.ENTITY, 'rule-node-config.originator-entity-by-name-pattern-desc'], + ] +); +export const allowedOriginatorFields: EntityField[] = [ + entityFields.createdTime, + entityFields.name, + {value: 'type', name: 'rule-node-config.profile-name', keyName: 'originatorProfileName'}, + entityFields.firstName, + entityFields.lastName, + entityFields.email, + entityFields.title, + entityFields.country, + entityFields.state, + entityFields.city, + entityFields.address, + entityFields.address2, + entityFields.zip, + entityFields.phone, + entityFields.label, + {value: 'id', name: 'rule-node-config.id', keyName: 'id'}, + {value: 'additionalInfo', name: 'rule-node-config.additional-info', keyName: 'additionalInfo'} +]; + +export const OriginatorFieldsMappingValues = new Map( + [ + ['type', 'profileName'], + ['createdTime', 'createdTime'], + ['name', 'name'], + ['firstName', 'firstName'], + ['lastName', 'lastName'], + ['email', 'email'], + ['title', 'title'], + ['country', 'country'], + ['state', 'state'], + ['city', 'city'], + ['address', 'address'], + ['address2', 'address2'], + ['zip', 'zip'], + ['phone', 'phone'], + ['label', 'label'], + ['id', 'id'], + ['additionalInfo', 'additionalInfo'], + ] +); + +export enum PerimeterType { + CIRCLE = 'CIRCLE', + POLYGON = 'POLYGON' +} + +export const perimeterTypeTranslations = new Map( + [ + [PerimeterType.CIRCLE, 'rule-node-config.perimeter-circle'], + [PerimeterType.POLYGON, 'rule-node-config.perimeter-polygon'], + ] +); + +export enum TimeUnit { + MILLISECONDS = 'MILLISECONDS', + SECONDS = 'SECONDS', + MINUTES = 'MINUTES', + HOURS = 'HOURS', + DAYS = 'DAYS' +} + +export const timeUnitTranslations = new Map( + [ + [TimeUnit.MILLISECONDS, 'rule-node-config.time-unit-milliseconds'], + [TimeUnit.SECONDS, 'rule-node-config.time-unit-seconds'], + [TimeUnit.MINUTES, 'rule-node-config.time-unit-minutes'], + [TimeUnit.HOURS, 'rule-node-config.time-unit-hours'], + [TimeUnit.DAYS, 'rule-node-config.time-unit-days'] + ] +); + +export enum RangeUnit { + METER = 'METER', + KILOMETER = 'KILOMETER', + FOOT = 'FOOT', + MILE = 'MILE', + NAUTICAL_MILE = 'NAUTICAL_MILE' +} + +export const rangeUnitTranslations = new Map( + [ + [RangeUnit.METER, 'rule-node-config.range-unit-meter'], + [RangeUnit.KILOMETER, 'rule-node-config.range-unit-kilometer'], + [RangeUnit.FOOT, 'rule-node-config.range-unit-foot'], + [RangeUnit.MILE, 'rule-node-config.range-unit-mile'], + [RangeUnit.NAUTICAL_MILE, 'rule-node-config.range-unit-nautical-mile'] + ] +); + +export enum EntityDetailsField { + ID = 'ID', + TITLE = 'TITLE', + COUNTRY = 'COUNTRY', + STATE = 'STATE', + CITY = 'CITY', + ZIP = 'ZIP', + ADDRESS = 'ADDRESS', + ADDRESS2 = 'ADDRESS2', + PHONE = 'PHONE', + EMAIL = 'EMAIL', + ADDITIONAL_INFO = 'ADDITIONAL_INFO' +} + +export interface SvMapOption { + name: string; + value: any; +} + +export const entityDetailsTranslations = new Map( + [ + [EntityDetailsField.ID, 'rule-node-config.entity-details-id'], + [EntityDetailsField.TITLE, 'rule-node-config.entity-details-title'], + [EntityDetailsField.COUNTRY, 'rule-node-config.entity-details-country'], + [EntityDetailsField.STATE, 'rule-node-config.entity-details-state'], + [EntityDetailsField.CITY, 'rule-node-config.entity-details-city'], + [EntityDetailsField.ZIP, 'rule-node-config.entity-details-zip'], + [EntityDetailsField.ADDRESS, 'rule-node-config.entity-details-address'], + [EntityDetailsField.ADDRESS2, 'rule-node-config.entity-details-address2'], + [EntityDetailsField.PHONE, 'rule-node-config.entity-details-phone'], + [EntityDetailsField.EMAIL, 'rule-node-config.entity-details-email'], + [EntityDetailsField.ADDITIONAL_INFO, 'rule-node-config.entity-details-additional_info'] + ] +); + +export enum FetchMode { + FIRST = 'FIRST', + LAST = 'LAST', + ALL = 'ALL' +} + +export const deduplicationStrategiesTranslations = new Map( + [ + [FetchMode.FIRST, 'rule-node-config.first'], + [FetchMode.LAST, 'rule-node-config.last'], + [FetchMode.ALL, 'rule-node-config.all'] + ] +); + +export const deduplicationStrategiesHintTranslations = new Map( + [ + [FetchMode.FIRST, 'rule-node-config.first-mode-hint'], + [FetchMode.LAST, 'rule-node-config.last-mode-hint'], + [FetchMode.ALL, 'rule-node-config.all-mode-hint'] + ] +); + +export enum SamplingOrder { + ASC = 'ASC', + DESC = 'DESC' +} + +export enum DataToFetch { + ATTRIBUTES = 'ATTRIBUTES', + LATEST_TELEMETRY = 'LATEST_TELEMETRY', + FIELDS = 'FIELDS' +} + +export const dataToFetchTranslations = new Map( + [ + [DataToFetch.ATTRIBUTES, 'rule-node-config.attributes'], + [DataToFetch.LATEST_TELEMETRY, 'rule-node-config.latest-telemetry'], + [DataToFetch.FIELDS, 'rule-node-config.fields'] + ] +); + +export const msgMetadataLabelTranslations = new Map( + [ + [DataToFetch.ATTRIBUTES, 'rule-node-config.add-mapped-attribute-to'], + [DataToFetch.LATEST_TELEMETRY, 'rule-node-config.add-mapped-latest-telemetry-to'], + [DataToFetch.FIELDS, 'rule-node-config.add-mapped-fields-to'] + ] +); + +export const samplingOrderTranslations = new Map( + [ + [SamplingOrder.ASC, 'rule-node-config.ascending'], + [SamplingOrder.DESC, 'rule-node-config.descending'] + ] +); + +export enum SqsQueueType { + STANDARD = 'STANDARD', + FIFO = 'FIFO' +} + +export const sqsQueueTypeTranslations = new Map( + [ + [SqsQueueType.STANDARD, 'rule-node-config.sqs-queue-standard'], + [SqsQueueType.FIFO, 'rule-node-config.sqs-queue-fifo'], + ] +); + +export type credentialsType = 'anonymous' | 'basic' | 'cert.PEM'; +export const credentialsTypes: credentialsType[] = ['anonymous', 'basic', 'cert.PEM']; + +export const credentialsTypeTranslations = new Map( + [ + ['anonymous', 'rule-node-config.credentials-anonymous'], + ['basic', 'rule-node-config.credentials-basic'], + ['cert.PEM', 'rule-node-config.credentials-pem'] + ] +); + +export type AzureIotHubCredentialsType = 'sas' | 'cert.PEM'; +export const azureIotHubCredentialsTypes: AzureIotHubCredentialsType[] = ['sas', 'cert.PEM']; + +export const azureIotHubCredentialsTypeTranslations = new Map( + [ + ['sas', 'rule-node-config.credentials-sas'], + ['cert.PEM', 'rule-node-config.credentials-pem'] + ] +); + +export enum HttpRequestType { + GET = 'GET', + POST = 'POST', + PUT = 'PUT', + DELETE = 'DELETE' +} + +export const ToByteStandartCharsetTypes = [ + 'US-ASCII', + 'ISO-8859-1', + 'UTF-8', + 'UTF-16BE', + 'UTF-16LE', + 'UTF-16' +]; + +export const ToByteStandartCharsetTypeTranslations = new Map( + [ + ['US-ASCII', 'rule-node-config.charset-us-ascii'], + ['ISO-8859-1', 'rule-node-config.charset-iso-8859-1'], + ['UTF-8', 'rule-node-config.charset-utf-8'], + ['UTF-16BE', 'rule-node-config.charset-utf-16be'], + ['UTF-16LE', 'rule-node-config.charset-utf-16le'], + ['UTF-16', 'rule-node-config.charset-utf-16'], + ] +); + +export interface RelationsQuery { + fetchLastLevelOnly: boolean; + direction: EntitySearchDirection; + maxLevel?: number; + filters?: EntityTypeFilter[]; +} + +export interface FunctionData { + value: MathFunction; + name: string; + description: string; + minArgs: number; + maxArgs: number; +} + +export enum MathFunction { + CUSTOM = 'CUSTOM', + ADD = 'ADD', + SUB = 'SUB', + MULT = 'MULT', + DIV = 'DIV', + SIN = 'SIN', + SINH = 'SINH', + COS = 'COS', + COSH = 'COSH', + TAN = 'TAN', + TANH = 'TANH', + ACOS = 'ACOS', + ASIN = 'ASIN', + ATAN = 'ATAN', + ATAN2 = 'ATAN2', + EXP = 'EXP', + EXPM1 = 'EXPM1', + SQRT = 'SQRT', + CBRT = 'CBRT', + GET_EXP = 'GET_EXP', + HYPOT = 'HYPOT', + LOG = 'LOG', + LOG10 = 'LOG10', + LOG1P = 'LOG1P', + CEIL = 'CEIL', + FLOOR = 'FLOOR', + FLOOR_DIV = 'FLOOR_DIV', + FLOOR_MOD = 'FLOOR_MOD', + ABS = 'ABS', + MIN = 'MIN', + MAX = 'MAX', + POW = 'POW', + SIGNUM = 'SIGNUM', + RAD = 'RAD', + DEG = 'DEG', +} + +export const MathFunctionMap = new Map( + [ + [ + MathFunction.CUSTOM, + { + value: MathFunction.CUSTOM, + name: 'Custom Function', + description: 'Use this function to specify complex mathematical expression.', + minArgs: 1, + maxArgs: 16 + } + ], + [ + MathFunction.ADD, + { + value: MathFunction.ADD, + name: 'Addition', + description: 'x + y', + minArgs: 2, + maxArgs: 2 + } + ], + [ + MathFunction.SUB, + { + value: MathFunction.SUB, + name: 'Subtraction', + description: 'x - y', + minArgs: 2, + maxArgs: 2 + } + ], + [ + MathFunction.MULT, + { + value: MathFunction.MULT, + name: 'Multiplication', + description: 'x * y', + minArgs: 2, + maxArgs: 2 + } + ], + [ + MathFunction.DIV, + { + value: MathFunction.DIV, + name: 'Division', + description: 'x / y', + minArgs: 2, + maxArgs: 2 + } + ], + [ + MathFunction.SIN, + { + value: MathFunction.SIN, + name: 'Sine', + description: 'Returns the trigonometric sine of an angle in radians.', + minArgs: 1, + maxArgs: 1 + } + ], + [ + MathFunction.SINH, + { + value: MathFunction.SINH, + name: 'Hyperbolic sine', + description: 'Returns the hyperbolic sine of an argument.', + minArgs: 1, + maxArgs: 1 + } + ], + [ + MathFunction.COS, + { + value: MathFunction.COS, + name: 'Cosine', + description: 'Returns the trigonometric cosine of an angle in radians.', + minArgs: 1, + maxArgs: 1 + } + ], + [ + MathFunction.COSH, + { + value: MathFunction.COSH, + name: 'Hyperbolic cosine', + description: 'Returns the hyperbolic cosine of an argument.', + minArgs: 1, + maxArgs: 1 + } + ], + [ + MathFunction.TAN, + { + value: MathFunction.TAN, + name: 'Tangent', + description: 'Returns the trigonometric tangent of an angle in radians', + minArgs: 1, + maxArgs: 1 + } + ], + [ + MathFunction.TANH, + { + value: MathFunction.TANH, + name: 'Hyperbolic tangent', + description: 'Returns the hyperbolic tangent of an argument', + minArgs: 1, + maxArgs: 1 + } + ], + [ + MathFunction.ACOS, + { + value: MathFunction.ACOS, + name: 'Arc cosine', + description: 'Returns the arc cosine of an argument', + minArgs: 1, + maxArgs: 1 + } + ], + [ + MathFunction.ASIN, + { + value: MathFunction.ASIN, + name: 'Arc sine', + description: 'Returns the arc sine of an argument', + minArgs: 1, + maxArgs: 1 + } + ], + [ + MathFunction.ATAN, + { + value: MathFunction.ATAN, + name: 'Arc tangent', + description: 'Returns the arc tangent of an argument', + minArgs: 1, + maxArgs: 1 + } + ], + [ + MathFunction.ATAN2, + { + value: MathFunction.ATAN2, + name: '2-argument arc tangent', + description: 'Returns the angle theta from the conversion of rectangular coordinates (x, y) to polar coordinates (r, theta)', + minArgs: 2, + maxArgs: 2 + } + ], + [ + MathFunction.EXP, + { + value: MathFunction.EXP, + name: 'Exponential', + description: 'Returns Euler\'s number e raised to the power of an argument', + minArgs: 1, + maxArgs: 1 + } + ], + [ + MathFunction.EXPM1, + { + value: MathFunction.EXPM1, + name: 'Exponential minus one', + description: 'Returns Euler\'s number e raised to the power of an argument minus one', + minArgs: 1, + maxArgs: 1 + } + ], + [ + MathFunction.SQRT, + { + value: MathFunction.SQRT, + name: 'Square', + description: 'Returns the correctly rounded positive square root of an argument', + minArgs: 1, + maxArgs: 1 + } + ], + [ + MathFunction.CBRT, + { + value: MathFunction.CBRT, + name: 'Cube root', + description: 'Returns the cube root of an argument', + minArgs: 1, + maxArgs: 1 + } + ], + [ + MathFunction.GET_EXP, + { + value: MathFunction.GET_EXP, + name: 'Get exponent', + description: 'Returns the unbiased exponent used in the representation of an argument', + minArgs: 1, + maxArgs: 1 + } + ], + [ + MathFunction.HYPOT, + { + value: MathFunction.HYPOT, + name: 'Square root', + description: 'Returns the square root of the squares of the arguments', + minArgs: 2, + maxArgs: 2 + } + ], + [ + MathFunction.LOG, + { + value: MathFunction.LOG, + name: 'Logarithm', + description: 'Returns the natural logarithm of an argument', + minArgs: 1, + maxArgs: 1 + } + ], + [ + MathFunction.LOG10, + { + value: MathFunction.LOG10, + name: 'Base 10 logarithm', + description: 'Returns the base 10 logarithm of an argument', + minArgs: 1, + maxArgs: 1 + } + ], + [ + MathFunction.LOG1P, + { + value: MathFunction.LOG1P, + name: 'Logarithm of the sum', + description: 'Returns the natural logarithm of the sum of an argument', + minArgs: 1, + maxArgs: 1 + } + ], + [ + MathFunction.CEIL, + { + value: MathFunction.CEIL, + name: 'Ceiling', + description: 'Returns the smallest (closest to negative infinity) of an argument', + minArgs: 1, + maxArgs: 1 + } + ], + [ + MathFunction.FLOOR, + { + value: MathFunction.FLOOR, + name: 'Floor', + description: 'Returns the largest (closest to positive infinity) of an argument', + minArgs: 1, + maxArgs: 1 + } + ], + [ + MathFunction.FLOOR_DIV, + { + value: MathFunction.FLOOR_DIV, + name: 'Floor division', + description: 'Returns the largest (closest to positive infinity) of the arguments', + minArgs: 2, + maxArgs: 2 + } + ], + [ + MathFunction.FLOOR_MOD, + { + value: MathFunction.FLOOR_MOD, + name: 'Floor modulus', + description: 'Returns the floor modulus of the arguments', + minArgs: 2, + maxArgs: 2 + } + ], + [ + MathFunction.ABS, + { + value: MathFunction.ABS, + name: 'Absolute', + description: 'Returns the absolute value of an argument', + minArgs: 1, + maxArgs: 1 + } + ], + [ + MathFunction.MIN, + { + value: MathFunction.MIN, + name: 'Min', + description: 'Returns the smaller of the arguments', + minArgs: 2, + maxArgs: 2 + } + ], + [ + MathFunction.MAX, + { + value: MathFunction.MAX, + name: 'Max', + description: 'Returns the greater of the arguments', + minArgs: 2, + maxArgs: 2 + } + ], + [ + MathFunction.POW, + { + value: MathFunction.POW, + name: 'Raise to a power', + description: 'Returns the value of the first argument raised to the power of the second argument', + minArgs: 2, + maxArgs: 2 + } + ], + [ + MathFunction.SIGNUM, + { + value: MathFunction.SIGNUM, + name: 'Sign of a real number', + description: 'Returns the signum function of the argument', + minArgs: 1, + maxArgs: 1 + } + ], + [ + MathFunction.RAD, + { + value: MathFunction.RAD, + name: 'Radian', + description: 'Converts an angle measured in degrees to an approximately equivalent angle measured in radians', + minArgs: 1, + maxArgs: 1 + } + ], + [ + MathFunction.DEG, + { + value: MathFunction.DEG, + name: 'Degrees', + description: 'Converts an angle measured in radians to an approximately equivalent angle measured in degrees.', + minArgs: 1, + maxArgs: 1 + } + ], + ]); + +export enum ArgumentType { + MESSAGE_BODY = 'MESSAGE_BODY', + MESSAGE_METADATA = 'MESSAGE_METADATA', + ATTRIBUTE = 'ATTRIBUTE', + TIME_SERIES = 'TIME_SERIES', + CONSTANT = 'CONSTANT' +} + +export enum ArgumentTypeResult { + MESSAGE_BODY = 'MESSAGE_BODY', + MESSAGE_METADATA = 'MESSAGE_METADATA', + ATTRIBUTE = 'ATTRIBUTE', + TIME_SERIES = 'TIME_SERIES' +} + +export enum FetchTo { + DATA = 'DATA', + METADATA = 'METADATA' +} + +export const FetchFromToTranslation = new Map([ + [FetchTo.DATA, 'rule-node-config.message-to-metadata'], + [FetchTo.METADATA, 'rule-node-config.metadata-to-message'], +]); + +export const FetchFromTranslation = new Map([ + [FetchTo.DATA, 'rule-node-config.from-message'], + [FetchTo.METADATA, 'rule-node-config.from-metadata'], +]); + +export const FetchToTranslation = new Map([ + [FetchTo.DATA, 'rule-node-config.message'], + [FetchTo.METADATA, 'rule-node-config.metadata'], +]); + +export const FetchToRenameTranslation = new Map([ + [FetchTo.DATA, 'rule-node-config.message'], + [FetchTo.METADATA, 'rule-node-config.message-metadata'], +]); + +export interface ArgumentTypeData { + name: string; + description: string; +} + +export const ArgumentTypeMap = new Map([ + [ + ArgumentType.MESSAGE_BODY, + { + name: 'rule-node-config.message-body-type', + description: 'rule-node-config.message-body-type-description' + } + ], + [ + ArgumentType.MESSAGE_METADATA, + { + name: 'rule-node-config.message-metadata-type', + description: 'rule-node-config.message-metadata-type-description' + } + ], + [ + ArgumentType.ATTRIBUTE, + { + name: 'rule-node-config.attribute-type', + description: 'rule-node-config.attribute-type-description' + } + ], + [ + ArgumentType.TIME_SERIES, + { + name: 'rule-node-config.time-series-type', + description: 'rule-node-config.time-series-type-description' + } + ], + [ + ArgumentType.CONSTANT, + { + name: 'rule-node-config.constant-type', + description: 'rule-node-config.constant-type-description' + } + ] +]); + +export const ArgumentTypeResultMap = new Map([ + [ + ArgumentTypeResult.MESSAGE_BODY, + { + name: 'rule-node-config.message-body-type', + description: 'rule-node-config.message-body-type-result-description' + } + ], + [ + ArgumentTypeResult.MESSAGE_METADATA, + { + name: 'rule-node-config.message-metadata-type', + description: 'rule-node-config.message-metadata-result-description' + } + ], + [ + ArgumentTypeResult.ATTRIBUTE, + { + name: 'rule-node-config.attribute-type', + description: 'rule-node-config.attribute-type-result-description' + } + ], + [ + ArgumentTypeResult.TIME_SERIES, + { + name: 'rule-node-config.time-series-type', + description: 'rule-node-config.time-series-type-result-description' + } + ] +]); + +export const ArgumentName = ['x', 'y', 'z', 'a', 'b', 'c', 'd', 'k', 'l', 'm', 'n', 'o', 'p', 'r', 's', 't']; + +export enum AttributeScope { + SHARED_SCOPE = 'SHARED_SCOPE', + SERVER_SCOPE = 'SERVER_SCOPE', + CLIENT_SCOPE = 'CLIENT_SCOPE' +} + +export enum AttributeScopeResult { + SHARED_SCOPE = 'SHARED_SCOPE', + SERVER_SCOPE = 'SERVER_SCOPE' +} + +export const AttributeScopeMap = new Map([ + [AttributeScope.SHARED_SCOPE, 'rule-node-config.shared-scope'], + [AttributeScope.SERVER_SCOPE, 'rule-node-config.server-scope'], + [AttributeScope.CLIENT_SCOPE, 'rule-node-config.client-scope'] +]); + +export enum PresenceMonitoringStrategy { + ON_FIRST_MESSAGE = 'ON_FIRST_MESSAGE', + ON_EACH_MESSAGE = 'ON_EACH_MESSAGE' +} + +export interface PresenceMonitoringStrategyData { + value: boolean; + name: string; +} + +export const PresenceMonitoringStrategiesData = new Map([ + [ + PresenceMonitoringStrategy.ON_EACH_MESSAGE, + { + value: true, + name: 'rule-node-config.presence-monitoring-strategy-on-each-message' + } + ], + [ + PresenceMonitoringStrategy.ON_FIRST_MESSAGE, + { + value: false, + name: 'rule-node-config.presence-monitoring-strategy-on-first-message' + } + ] +]); + +export const IntLimit = 2147483648; diff --git a/ui-ngx/src/app/modules/home/components/rule-node/rule-node-config.module.ts b/ui-ngx/src/app/modules/home/components/rule-node/rule-node-config.module.ts new file mode 100644 index 0000000000..86512eceea --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/rule-node-config.module.ts @@ -0,0 +1,53 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { NgModule } from '@angular/core'; +import { EmptyConfigComponent } from './empty-config.component'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@shared/shared.module'; +import { ActionRuleNodeConfigModule } from '@home/components/rule-node/action/action-rule-node-config.module'; +import { FilterRuleNodeConfigModule } from '@home/components/rule-node/filter/filter-rule-node-config.module'; +import { EnrichmentRuleNodeCoreModule } from '@home/components/rule-node/enrichment/enrichment-rule-node-core.module'; +import { ExternalRuleNodeConfigModule } from '@home/components/rule-node/external/external-rule-node-config.module'; +import { + TransformationRuleNodeConfigModule +} from '@home/components/rule-node/transformation/transformation-rule-node-config.module'; +import { FlowRuleNodeConfigModule } from '@home/components/rule-node/flow/flow-rule-node-config.module'; +import { RuleChainService } from '@core/http/rule-chain.service'; + +@NgModule({ + declarations: [ + EmptyConfigComponent + ], + imports: [ + CommonModule, + SharedModule + ], + exports: [ + ActionRuleNodeConfigModule, + FilterRuleNodeConfigModule, + EnrichmentRuleNodeCoreModule, + ExternalRuleNodeConfigModule, + TransformationRuleNodeConfigModule, + FlowRuleNodeConfigModule, + EmptyConfigComponent + ] +}) +export class RuleNodeConfigModule { + constructor(private ruleChainService: RuleChainService) { + this.ruleChainService.registerSystemRuleNodeConfigModule(this.constructor); + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/transformation/change-originator-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/transformation/change-originator-config.component.html new file mode 100644 index 0000000000..6a1d59e220 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/transformation/change-originator-config.component.html @@ -0,0 +1,66 @@ + +
    + + rule-node-config.new-originator + + + + {{ originatorSourceTranslationMap.get(changeOriginatorConfigForm.get('originatorSource').value) | translate }} + + + + + {{ originatorSourceTranslationMap.get(source) | translate }} + +
    + + {{ originatorSourceDescTranslationMap.get(source) | translate }} + +
    +
    +
    +
    + + +
    + + + + rule-node-config.entity-name-pattern + + + {{ 'rule-node-config.entity-name-pattern-required' | translate }} + + +
    +
    + + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/transformation/change-originator-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/transformation/change-originator-config.component.ts new file mode 100644 index 0000000000..954dd412e5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/transformation/change-originator-config.component.ts @@ -0,0 +1,83 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { + OriginatorSource, + originatorSourceDescTranslations, + originatorSourceTranslations +} from '@home/components/rule-node/rule-node-config.models'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@app/shared/models/rule-node.models'; +import { EntityType } from '@app/shared/models/entity-type.models'; + +@Component({ + selector: 'tb-transformation-node-change-originator-config', + templateUrl: './change-originator-config.component.html' +}) +export class ChangeOriginatorConfigComponent extends RuleNodeConfigurationComponent { + + originatorSource = OriginatorSource; + originatorSources = Object.keys(OriginatorSource) as OriginatorSource[]; + originatorSourceTranslationMap = originatorSourceTranslations; + originatorSourceDescTranslationMap = originatorSourceDescTranslations; + + changeOriginatorConfigForm: FormGroup; + + allowedEntityTypes = [EntityType.DEVICE, EntityType.ASSET, EntityType.ENTITY_VIEW, EntityType.USER, EntityType.EDGE]; + + constructor(private fb: FormBuilder) { + super(); + } + + protected configForm(): FormGroup { + return this.changeOriginatorConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.changeOriginatorConfigForm = this.fb.group({ + originatorSource: [configuration ? configuration.originatorSource : null, [Validators.required]], + entityType: [configuration ? configuration.entityType : null, []], + entityNamePattern: [configuration ? configuration.entityNamePattern : null, []], + relationsQuery: [configuration ? configuration.relationsQuery : null, []] + }); + } + + protected validatorTriggers(): string[] { + return ['originatorSource']; + } + + protected updateValidators(emitEvent: boolean) { + const originatorSource: OriginatorSource = this.changeOriginatorConfigForm.get('originatorSource').value; + if (originatorSource === OriginatorSource.RELATED) { + this.changeOriginatorConfigForm.get('relationsQuery').setValidators([Validators.required]); + } else { + this.changeOriginatorConfigForm.get('relationsQuery').setValidators([]); + } + if (originatorSource === OriginatorSource.ENTITY) { + this.changeOriginatorConfigForm.get('entityType').setValidators([Validators.required]); + this.changeOriginatorConfigForm.get('entityNamePattern').setValidators([Validators.required, Validators.pattern(/.*\S.*/)]); + } else { + this.changeOriginatorConfigForm.get('entityType').patchValue(null, {emitEvent}); + this.changeOriginatorConfigForm.get('entityNamePattern').patchValue(null, {emitEvent}); + this.changeOriginatorConfigForm.get('entityType').setValidators([]); + this.changeOriginatorConfigForm.get('entityNamePattern').setValidators([]); + } + this.changeOriginatorConfigForm.get('relationsQuery').updateValueAndValidity({emitEvent}); + this.changeOriginatorConfigForm.get('entityType').updateValueAndValidity({emitEvent}); + this.changeOriginatorConfigForm.get('entityNamePattern').updateValueAndValidity({emitEvent}); + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/transformation/copy-keys-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/transformation/copy-keys-config.component.html new file mode 100644 index 0000000000..ccaa44bea1 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/transformation/copy-keys-config.component.html @@ -0,0 +1,36 @@ + +
    + + + + + help + + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/transformation/copy-keys-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/transformation/copy-keys-config.component.ts new file mode 100644 index 0000000000..1fcf4a7053 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/transformation/copy-keys-config.component.ts @@ -0,0 +1,73 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { isDefinedAndNotNull } from '@core/public-api'; +import { TranslateService } from '@ngx-translate/core'; +import { FetchFromToTranslation, FetchTo } from '../rule-node-config.models'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@app/shared/models/rule-node.models'; + +@Component({ + selector: 'tb-transformation-node-copy-keys-config', + templateUrl: './copy-keys-config.component.html', + styleUrls: [] +}) + +export class CopyKeysConfigComponent extends RuleNodeConfigurationComponent{ + copyKeysConfigForm: FormGroup; + copyFrom = []; + translation = FetchFromToTranslation; + + constructor(private fb: FormBuilder, + private translate: TranslateService) { + super(); + for (const key of this.translation.keys()) { + this.copyFrom.push({ + value: key, + name: this.translate.instant(this.translation.get(key)) + }); + } + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.copyKeysConfigForm = this.fb.group({ + copyFrom: [configuration.copyFrom , [Validators.required]], + keys: [configuration ? configuration.keys : null, [Validators.required]] + }); + } + + protected configForm(): FormGroup { + return this.copyKeysConfigForm; + } + + protected prepareInputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + let copyFrom: FetchTo; + + if (isDefinedAndNotNull(configuration?.fromMetadata)) { + copyFrom = configuration.copyFrom ? FetchTo.METADATA : FetchTo.DATA; + } else if (isDefinedAndNotNull(configuration?.copyFrom)) { + copyFrom = configuration.copyFrom; + } else { + copyFrom = FetchTo.DATA; + } + + return { + keys: isDefinedAndNotNull(configuration?.keys) ? configuration.keys : null, + copyFrom + }; + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/transformation/deduplication-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/transformation/deduplication-config.component.html new file mode 100644 index 0000000000..e85ea71399 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/transformation/deduplication-config.component.html @@ -0,0 +1,100 @@ + +
    + + {{'rule-node-config.interval' | translate}} + + + {{'rule-node-config.interval-required' | translate}} + + + {{'rule-node-config.interval-min-error' | translate}} + + help + +
    +
    +
    rule-node-config.strategy
    + + + {{ deduplicationStrategiesTranslations.get(strategy) | translate }} + + + + + + + + +
    + + +
    +
    +
    + + + rule-node-config.advanced-settings + +
    + + {{'rule-node-config.max-pending-msgs' | translate}} + + + {{'rule-node-config.max-pending-msgs-required' | translate}} + + + {{'rule-node-config.max-pending-msgs-max-error' | translate}} + + + {{'rule-node-config.max-pending-msgs-min-error' | translate}} + + help + + + {{'rule-node-config.max-retries' | translate}} + + + {{'rule-node-config.max-retries-required' | translate}} + + + {{'rule-node-config.max-retries-max-error' | translate}} + + + {{'rule-node-config.max-retries-min-error' | translate}} + + help + +
    +
    +
    +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/transformation/deduplication-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/transformation/deduplication-config.component.ts new file mode 100644 index 0000000000..bf50b36f3a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/transformation/deduplication-config.component.ts @@ -0,0 +1,79 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { isDefinedAndNotNull } from '@core/public-api'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { deduplicationStrategiesTranslations, FetchMode } from '@home/components/rule-node/rule-node-config.models'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; + +@Component({ + selector: 'tb-transformation-node-deduplication-config', + templateUrl: './deduplication-config.component.html', + styleUrls: [] +}) + +export class DeduplicationConfigComponent extends RuleNodeConfigurationComponent { + + deduplicationConfigForm: FormGroup; + deduplicationStrategie = FetchMode; + deduplicationStrategies = Object.keys(this.deduplicationStrategie); + deduplicationStrategiesTranslations = deduplicationStrategiesTranslations; + + constructor(private fb: FormBuilder) { + super(); + } + + protected configForm(): FormGroup { + return this.deduplicationConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.deduplicationConfigForm = this.fb.group({ + interval: [isDefinedAndNotNull(configuration?.interval) ? configuration.interval : null, [Validators.required, + Validators.min(1)]], + strategy: [isDefinedAndNotNull(configuration?.strategy) ? configuration.strategy : null, [Validators.required]], + outMsgType: [isDefinedAndNotNull(configuration?.outMsgType) ? configuration.outMsgType : null, [Validators.required]], + maxPendingMsgs: [isDefinedAndNotNull(configuration?.maxPendingMsgs) ? configuration.maxPendingMsgs : null, [Validators.required, + Validators.min(1), Validators.max(1000)]], + maxRetries: [isDefinedAndNotNull(configuration?.maxRetries) ? configuration.maxRetries : null, + [Validators.required, Validators.min(0), Validators.max(100)]] + }); + } + + protected prepareInputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + if (!configuration) { + configuration = {}; + } + if (!configuration.outMsgType) { + configuration.outMsgType = 'POST_TELEMETRY_REQUEST'; + } + return super.prepareInputConfig(configuration); + } + + protected updateValidators(emitEvent: boolean) { + if (this.deduplicationConfigForm.get('strategy').value === this.deduplicationStrategie.ALL) { + this.deduplicationConfigForm.get('outMsgType').enable({emitEvent: false}); + } else { + this.deduplicationConfigForm.get('outMsgType').disable({emitEvent: false}); + } + this.deduplicationConfigForm.get('outMsgType').updateValueAndValidity({emitEvent}); + } + + protected validatorTriggers(): string[] { + return ['strategy']; + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/transformation/delete-keys-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/transformation/delete-keys-config.component.html new file mode 100644 index 0000000000..2bab63f4d6 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/transformation/delete-keys-config.component.html @@ -0,0 +1,35 @@ + +
    + + + + + help + + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/transformation/delete-keys-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/transformation/delete-keys-config.component.ts new file mode 100644 index 0000000000..a5e95ef6ce --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/transformation/delete-keys-config.component.ts @@ -0,0 +1,74 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { isDefinedAndNotNull } from '@core/public-api'; +import { TranslateService } from '@ngx-translate/core'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@app/shared/models/rule-node.models'; +import { FetchTo, FetchToTranslation } from '@home/components/rule-node/rule-node-config.models'; + +@Component({ + selector: 'tb-transformation-node-delete-keys-config', + templateUrl: './delete-keys-config.component.html', + styleUrls: [] +}) + +export class DeleteKeysConfigComponent extends RuleNodeConfigurationComponent { + + deleteKeysConfigForm: FormGroup; + deleteFrom = []; + translation = FetchToTranslation; + + constructor(private fb: FormBuilder, + private translate: TranslateService) { + super(); + for (const key of this.translation.keys()) { + this.deleteFrom.push({ + value: key, + name: this.translate.instant(this.translation.get(key)) + }); + } + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.deleteKeysConfigForm = this.fb.group({ + deleteFrom: [configuration.deleteFrom, [Validators.required]], + keys: [configuration ? configuration.keys : null, [Validators.required]] + }); + } + + protected prepareInputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + let deleteFrom: FetchTo; + + if (isDefinedAndNotNull(configuration?.fromMetadata)) { + deleteFrom = configuration.fromMetadata ? FetchTo.METADATA : FetchTo.DATA; + } else if (isDefinedAndNotNull(configuration?.deleteFrom)) { + deleteFrom = configuration?.deleteFrom; + } else { + deleteFrom = FetchTo.DATA; + } + + return { + keys: isDefinedAndNotNull(configuration?.keys) ? configuration.keys : null, + deleteFrom + }; + } + + protected configForm(): FormGroup { + return this.deleteKeysConfigForm; + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/transformation/node-json-path-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/transformation/node-json-path-config.component.html new file mode 100644 index 0000000000..94f8046b1b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/transformation/node-json-path-config.component.html @@ -0,0 +1,25 @@ + +
    + + {{ 'rule-node-config.json-path-expression' | translate }} + + {{ 'rule-node-config.json-path-expression-hint' | translate }} + {{ 'rule-node-config.json-path-expression-required' | translate }} + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/transformation/node-json-path-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/transformation/node-json-path-config.component.ts new file mode 100644 index 0000000000..47185b1d51 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/transformation/node-json-path-config.component.ts @@ -0,0 +1,44 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; + +@Component({ + selector: 'tb-transformation-node-json-path-config', + templateUrl: './node-json-path-config.component.html', + styleUrls: [] +}) + +export class NodeJsonPathConfigComponent extends RuleNodeConfigurationComponent { + + jsonPathConfigForm: FormGroup; + + constructor(private fb: FormBuilder) { + super(); + } + + protected configForm(): FormGroup { + return this.jsonPathConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.jsonPathConfigForm = this.fb.group({ + jsonPath: [configuration ? configuration.jsonPath : null, [Validators.required]], + }); + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/transformation/rename-keys-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/transformation/rename-keys-config.component.html new file mode 100644 index 0000000000..7543875f19 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/transformation/rename-keys-config.component.html @@ -0,0 +1,40 @@ + +
    +
    rule-node-config.rename-keys-in
    +
    +
    + + + {{ data.name }} + + +
    +
    + + +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/transformation/rename-keys-config.component.scss b/ui-ngx/src/app/modules/home/components/rule-node/transformation/rename-keys-config.component.scss new file mode 100644 index 0000000000..1c767d5d68 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/transformation/rename-keys-config.component.scss @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + .fetch-to-data-toggle { + max-width: 420px; + width: 100%; + } + + .fx-centered { + display: flex; + width: 100%; + justify-content: space-around; + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/transformation/rename-keys-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/transformation/rename-keys-config.component.ts new file mode 100644 index 0000000000..ebc7393b05 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/transformation/rename-keys-config.component.ts @@ -0,0 +1,73 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { isDefinedAndNotNull } from '@core/public-api'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; +import { FetchTo, FetchToRenameTranslation } from '../rule-node-config.models'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; + + +@Component({ + selector: 'tb-transformation-node-rename-keys-config', + templateUrl: './rename-keys-config.component.html', + styleUrls: ['./rename-keys-config.component.scss'] +}) +export class RenameKeysConfigComponent extends RuleNodeConfigurationComponent { + renameKeysConfigForm: FormGroup; + renameIn = []; + translation = FetchToRenameTranslation; + + constructor(private fb: FormBuilder, + private translate: TranslateService) { + super(); + for (const key of this.translation.keys()) { + this.renameIn.push({ + value: key, + name: this.translate.instant(this.translation.get(key)) + }); + } + } + + protected configForm(): FormGroup { + return this.renameKeysConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.renameKeysConfigForm = this.fb.group({ + renameIn: [configuration ? configuration.renameIn : null, [Validators.required]], + renameKeysMapping: [configuration ? configuration.renameKeysMapping : null, [Validators.required]] + }); + } + + protected prepareInputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + let renameIn: FetchTo; + + if (isDefinedAndNotNull(configuration?.fromMetadata)) { + renameIn = configuration.fromMetadata ? FetchTo.METADATA : FetchTo.DATA; + } else if (isDefinedAndNotNull(configuration?.renameIn)) { + renameIn = configuration?.renameIn; + } else { + renameIn = FetchTo.DATA; + } + + return { + renameKeysMapping: isDefinedAndNotNull(configuration?.renameKeysMapping) ? configuration.renameKeysMapping : null, + renameIn + }; + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/transformation/script-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/transformation/script-config.component.html new file mode 100644 index 0000000000..efabc8d940 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/transformation/script-config.component.html @@ -0,0 +1,59 @@ + +
    + + + + + + + +
    + +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/rule-node/transformation/script-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/transformation/script-config.component.ts new file mode 100644 index 0000000000..848a9dd2e9 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/transformation/script-config.component.ts @@ -0,0 +1,128 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, EventEmitter, ViewChild } from '@angular/core'; +import { getCurrentAuthState, NodeScriptTestService } from '@core/public-api'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; +import type { JsFuncComponent } from '@shared/components/js-func.component'; +import { + RuleNodeConfiguration, + RuleNodeConfigurationComponent, + ScriptLanguage +} from '@app/shared/models/rule-node.models'; +import { DebugRuleNodeEventBody } from '@shared/models/event.models'; + +@Component({ + selector: 'tb-transformation-node-script-config', + templateUrl: './script-config.component.html', + styleUrls: [] +}) +export class TransformScriptConfigComponent extends RuleNodeConfigurationComponent { + + @ViewChild('jsFuncComponent', {static: false}) jsFuncComponent: JsFuncComponent; + @ViewChild('tbelFuncComponent', {static: false}) tbelFuncComponent: JsFuncComponent; + + scriptConfigForm: FormGroup; + + tbelEnabled = getCurrentAuthState(this.store).tbelEnabled; + + scriptLanguage = ScriptLanguage; + + changeScript: EventEmitter = new EventEmitter(); + + readonly hasScript = true; + + readonly testScriptLabel = 'rule-node-config.test-transformer-function'; + + constructor(private fb: FormBuilder, + private nodeScriptTestService: NodeScriptTestService, + private translate: TranslateService) { + super(); + } + + protected configForm(): FormGroup { + return this.scriptConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.scriptConfigForm = this.fb.group({ + scriptLang: [configuration ? configuration.scriptLang : ScriptLanguage.JS, [Validators.required]], + jsScript: [configuration ? configuration.jsScript : null, [Validators.required]], + tbelScript: [configuration ? configuration.tbelScript : null, []] + }); + } + + protected validatorTriggers(): string[] { + return ['scriptLang']; + } + + protected updateValidators(emitEvent: boolean) { + let scriptLang: ScriptLanguage = this.scriptConfigForm.get('scriptLang').value; + if (scriptLang === ScriptLanguage.TBEL && !this.tbelEnabled) { + scriptLang = ScriptLanguage.JS; + this.scriptConfigForm.get('scriptLang').patchValue(scriptLang, {emitEvent: false}); + setTimeout(() => { + this.scriptConfigForm.updateValueAndValidity({emitEvent: true}); + }); + } + this.scriptConfigForm.get('jsScript').setValidators(scriptLang === ScriptLanguage.JS ? [Validators.required] : []); + this.scriptConfigForm.get('jsScript').updateValueAndValidity({emitEvent}); + this.scriptConfigForm.get('tbelScript').setValidators(scriptLang === ScriptLanguage.TBEL ? [Validators.required] : []); + this.scriptConfigForm.get('tbelScript').updateValueAndValidity({emitEvent}); + } + + protected prepareInputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + if (configuration) { + if (!configuration.scriptLang) { + configuration.scriptLang = ScriptLanguage.JS; + } + } + return configuration; + } + + testScript(debugEventBody?: DebugRuleNodeEventBody) { + const scriptLang: ScriptLanguage = this.scriptConfigForm.get('scriptLang').value; + const scriptField = scriptLang === ScriptLanguage.JS ? 'jsScript' : 'tbelScript'; + const helpId = scriptLang === ScriptLanguage.JS + ? 'rulenode/transformation_node_script_fn' + : 'rulenode/tbel/transformation_node_script_fn'; + const script: string = this.scriptConfigForm.get(scriptField).value; + this.nodeScriptTestService.testNodeScript( + script, + 'update', + this.translate.instant('rule-node-config.transformer'), + 'Transform', + ['msg', 'metadata', 'msgType'], + this.ruleNodeId, + helpId, + scriptLang, + debugEventBody + ).subscribe((theScript) => { + if (theScript) { + this.scriptConfigForm.get(scriptField).setValue(theScript); + this.changeScript.emit(); + } + }); + } + + protected onValidate() { + const scriptLang: ScriptLanguage = this.scriptConfigForm.get('scriptLang').value; + if (scriptLang === ScriptLanguage.JS) { + this.jsFuncComponent.validateOnSubmit(); + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/transformation/to-email-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/transformation/to-email-config.component.html new file mode 100644 index 0000000000..373a4c6b57 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/transformation/to-email-config.component.html @@ -0,0 +1,139 @@ + +
    +
    +
    rule-node-config.email-sender
    +
    + + rule-node-config.from-template + + + {{ 'rule-node-config.email-from-template-hint' | translate }} + + +
    +
    +
    +
    + + {{ 'rule-node-config.from-template-required' | translate }} + +
    +
    +
    +
    +
    +
    rule-node-config.recipients
    + + +
    +
    + + rule-node-config.to-template + + + {{ 'rule-node-config.to-template-required' | translate }} + + + + rule-node-config.cc-template + + + + rule-node-config.bcc-template + + +
    +
    +
    +
    rule-node-config.message-subject-and-content
    + + +
    + + rule-node-config.subject-template + + + {{ 'rule-node-config.subject-template-required' | translate }} + + + + rule-node-config.mail-body-type + + + + {{ getBodyTypeName() | translate }} + + + + + {{ type.name | translate }} + +
    + + {{ type.description | translate }} + +
    +
    +
    + + rule-node-config.body-type-template + + rule-node-config.mail-body-types.after-template-evaluation-hint + + + rule-node-config.body-template + + + {{ 'rule-node-config.body-template-required' | translate }} + + +
    +
    +
    diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessorFactory.java b/ui-ngx/src/app/modules/home/components/rule-node/transformation/to-email-config.component.scss similarity index 60% rename from application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessorFactory.java rename to ui-ngx/src/app/modules/home/components/rule-node/transformation/to-email-config.component.scss index 6d98c01760..2a0c2e6ea3 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessorFactory.java +++ b/ui-ngx/src/app/modules/home/components/rule-node/transformation/to-email-config.component.scss @@ -13,14 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.edge.rpc.processor.alarm; +:host { + .input-bottom-double-hint { + display: inline-flex; -import org.springframework.stereotype.Component; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessorFactory; - -@Component -@TbCoreComponent -public class AlarmEdgeProcessorFactory extends BaseEdgeProcessorFactory { + & .see-example { + flex-shrink: 0; + padding-right: 16px; + } + } + textarea.tb-enable-vertical-resize { + resize: vertical; + } } diff --git a/ui-ngx/src/app/modules/home/components/rule-node/transformation/to-email-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/transformation/to-email-config.component.ts new file mode 100644 index 0000000000..97282025fd --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/transformation/to-email-config.component.ts @@ -0,0 +1,98 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { isDefinedAndNotNull } from '@core/public-api'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { RuleNodeConfiguration, RuleNodeConfigurationComponent } from '@app/shared/models/rule-node.models'; + +@Component({ + selector: 'tb-transformation-node-to-email-config', + templateUrl: './to-email-config.component.html', + styleUrls: ['./to-email-config.component.scss'] +}) +export class ToEmailConfigComponent extends RuleNodeConfigurationComponent { + + toEmailConfigForm: FormGroup; + mailBodyTypes = [ + { + name: 'rule-node-config.mail-body-types.plain-text', + description: 'rule-node-config.mail-body-types.plain-text-description', + value: 'false', + }, + { + name: 'rule-node-config.mail-body-types.html', + description: 'rule-node-config.mail-body-types.html-text-description', + value: 'true', + }, + { + name: 'rule-node-config.mail-body-types.use-body-type-template', + description: 'rule-node-config.mail-body-types.dynamic-text-description', + value: 'dynamic', + } + ]; + + constructor(private fb: FormBuilder) { + super(); + } + + protected configForm(): FormGroup { + return this.toEmailConfigForm; + } + + protected onConfigurationSet(configuration: RuleNodeConfiguration) { + this.toEmailConfigForm = this.fb.group({ + fromTemplate: [configuration ? configuration.fromTemplate : null, [Validators.required]], + toTemplate: [configuration ? configuration.toTemplate : null, [Validators.required]], + ccTemplate: [configuration ? configuration.ccTemplate : null, []], + bccTemplate: [configuration ? configuration.bccTemplate : null, []], + subjectTemplate: [configuration ? configuration.subjectTemplate : null, [Validators.required]], + mailBodyType: [configuration ? configuration.mailBodyType : null], + isHtmlTemplate: [configuration ? configuration.isHtmlTemplate : null, [Validators.required]], + bodyTemplate: [configuration ? configuration.bodyTemplate : null, [Validators.required]], + }); + } + + protected prepareInputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + return { + fromTemplate: isDefinedAndNotNull(configuration?.fromTemplate) ? configuration.fromTemplate : null, + toTemplate: isDefinedAndNotNull(configuration?.toTemplate) ? configuration.toTemplate : null, + ccTemplate: isDefinedAndNotNull(configuration?.ccTemplate) ? configuration.ccTemplate : null, + bccTemplate: isDefinedAndNotNull(configuration?.bccTemplate) ? configuration.bccTemplate : null, + subjectTemplate: isDefinedAndNotNull(configuration?.subjectTemplate) ? configuration.subjectTemplate : null, + mailBodyType: isDefinedAndNotNull(configuration?.mailBodyType) ? configuration.mailBodyType : null, + isHtmlTemplate: isDefinedAndNotNull(configuration?.isHtmlTemplate) ? configuration.isHtmlTemplate : null, + bodyTemplate: isDefinedAndNotNull(configuration?.bodyTemplate) ? configuration.bodyTemplate : null, + }; + } + + protected updateValidators(emitEvent: boolean) { + if (this.toEmailConfigForm.get('mailBodyType').value === 'dynamic') { + this.toEmailConfigForm.get('isHtmlTemplate').enable({emitEvent: false}); + } else { + this.toEmailConfigForm.get('isHtmlTemplate').disable({emitEvent: false}); + } + this.toEmailConfigForm.get('isHtmlTemplate').updateValueAndValidity({emitEvent}); + } + + protected validatorTriggers(): string[] { + return ['mailBodyType']; + } + + getBodyTypeName(): string { + return this.mailBodyTypes.find(type => type.value === this.toEmailConfigForm.get('mailBodyType').value).name; + } +} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/transformation/transformation-rule-node-config.module.ts b/ui-ngx/src/app/modules/home/components/rule-node/transformation/transformation-rule-node-config.module.ts new file mode 100644 index 0000000000..5104d3ac81 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/rule-node/transformation/transformation-rule-node-config.module.ts @@ -0,0 +1,58 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@shared/public-api'; +import { ChangeOriginatorConfigComponent } from './change-originator-config.component'; +import { CommonRuleNodeConfigModule } from '../common/common-rule-node-config.module'; +import { TransformScriptConfigComponent } from './script-config.component'; +import { ToEmailConfigComponent } from './to-email-config.component'; +import { CopyKeysConfigComponent } from './copy-keys-config.component'; +import { RenameKeysConfigComponent } from './rename-keys-config.component'; +import { NodeJsonPathConfigComponent } from './node-json-path-config.component'; +import { DeleteKeysConfigComponent } from './delete-keys-config.component'; +import { DeduplicationConfigComponent } from './deduplication-config.component'; + +@NgModule({ + declarations: [ + ChangeOriginatorConfigComponent, + TransformScriptConfigComponent, + ToEmailConfigComponent, + CopyKeysConfigComponent, + RenameKeysConfigComponent, + NodeJsonPathConfigComponent, + DeleteKeysConfigComponent, + DeduplicationConfigComponent + ], + imports: [ + CommonModule, + SharedModule, + CommonRuleNodeConfigModule + ], + exports: [ + ChangeOriginatorConfigComponent, + TransformScriptConfigComponent, + ToEmailConfigComponent, + CopyKeysConfigComponent, + RenameKeysConfigComponent, + NodeJsonPathConfigComponent, + DeleteKeysConfigComponent, + DeduplicationConfigComponent + ] +}) +export class TransformationRuleNodeConfigModule { +} diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts index 800deec7e5..8a71e04e1e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts @@ -14,10 +14,10 @@ /// limitations under the License. /// -import { NgModule, Type } from '@angular/core'; +import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { SharedModule } from '@shared/shared.module'; -import { IBasicWidgetConfigComponent } from '@home/components/widget/config/widget-config.component.models'; +import { WidgetService } from '@core/http/widget.service'; import { WidgetConfigComponentsModule } from '@home/components/widget/config/widget-config-components.module'; import { SimpleCardBasicConfigComponent @@ -146,6 +146,9 @@ import { ScadaSymbolBasicConfigComponent } from '@home/components/widget/config/ import { SegmentedButtonBasicConfigComponent } from '@home/components/widget/config/basic/button/segmented-button-basic-config.component'; +import { + ValueStepperBasicConfigComponent +} from '@home/components/widget/config/basic/rpc/value-stepper-basic-config.component'; @NgModule({ declarations: [ @@ -182,6 +185,7 @@ import { PowerButtonBasicConfigComponent, SliderBasicConfigComponent, ToggleButtonBasicConfigComponent, + ValueStepperBasicConfigComponent, TimeSeriesChartBasicConfigComponent, ComparisonKeyRowComponent, ComparisonKeysTableComponent, @@ -236,6 +240,7 @@ import { PowerButtonBasicConfigComponent, SliderBasicConfigComponent, ToggleButtonBasicConfigComponent, + ValueStepperBasicConfigComponent, TimeSeriesChartBasicConfigComponent, StatusWidgetBasicConfigComponent, PieChartBasicConfigComponent, @@ -251,47 +256,7 @@ import { ] }) export class BasicWidgetConfigModule { + constructor(private widgetService: WidgetService) { + this.widgetService.registerBasicWidgetConfigComponents(this.constructor) + } } - -export const basicWidgetConfigComponentsMap: {[key: string]: Type} = { - 'tb-simple-card-basic-config': SimpleCardBasicConfigComponent, - 'tb-entities-table-basic-config': EntitiesTableBasicConfigComponent, - 'tb-timeseries-table-basic-config': TimeseriesTableBasicConfigComponent, - 'tb-flot-basic-config': FlotBasicConfigComponent, - 'tb-alarms-table-basic-config': AlarmsTableBasicConfigComponent, - 'tb-value-card-basic-config': ValueCardBasicConfigComponent, - 'tb-aggregated-value-card-basic-config': AggregatedValueCardBasicConfigComponent, - 'tb-alarm-count-basic-config': AlarmCountBasicConfigComponent, - 'tb-entity-count-basic-config': EntityCountBasicConfigComponent, - 'tb-battery-level-basic-config': BatteryLevelBasicConfigComponent, - 'tb-wind-speed-direction-basic-config': WindSpeedDirectionBasicConfigComponent, - 'tb-signal-strength-basic-config': SignalStrengthBasicConfigComponent, - 'tb-value-chart-card-basic-config': ValueChartCardBasicConfigComponent, - 'tb-progress-bar-basic-config': ProgressBarBasicConfigComponent, - 'tb-radial-gauge-basic-config': RadialGaugeBasicConfigComponent, - 'tb-thermometer-scale-gauge-basic-config': ThermometerScaleGaugeBasicConfigComponent, - 'tb-compass-gauge-basic-config': CompassGaugeBasicConfigComponent, - 'tb-liquid-level-card-basic-config': LiquidLevelCardBasicConfigComponent, - 'tb-doughnut-basic-config': DoughnutBasicConfigComponent, - 'tb-range-chart-basic-config': RangeChartBasicConfigComponent, - 'tb-bar-chart-with-labels-basic-config': BarChartWithLabelsBasicConfigComponent, - 'tb-single-switch-basic-config': SingleSwitchBasicConfigComponent, - 'tb-action-button-basic-config': ActionButtonBasicConfigComponent, - 'tb-segmented-button-basic-config': SegmentedButtonBasicConfigComponent, - 'tb-command-button-basic-config': CommandButtonBasicConfigComponent, - 'tb-power-button-basic-config': PowerButtonBasicConfigComponent, - 'tb-slider-basic-config': SliderBasicConfigComponent, - 'tb-toggle-button-basic-config': ToggleButtonBasicConfigComponent, - 'tb-time-series-chart-basic-config': TimeSeriesChartBasicConfigComponent, - 'tb-status-widget-basic-config': StatusWidgetBasicConfigComponent, - 'tb-pie-chart-basic-config': PieChartBasicConfigComponent, - 'tb-bar-chart-basic-config': BarChartBasicConfigComponent, - 'tb-polar-area-chart-basic-config': PolarAreaChartBasicConfigComponent, - 'tb-radar-chart-basic-config': RadarChartBasicConfigComponent, - 'tb-digital-simple-gauge-basic-config': DigitalSimpleGaugeBasicConfigComponent, - 'tb-mobile-app-qr-code-basic-config': MobileAppQrCodeBasicConfigComponent, - 'tb-label-card-basic-config': LabelCardBasicConfigComponent, - 'tb-label-value-card-basic-config': LabelValueCardBasicConfigComponent, - 'tb-unread-notification-basic-config': UnreadNotificationBasicConfigComponent, - 'tb-scada-symbol-basic-config': ScadaSymbolBasicConfigComponent -}; diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/button/power-button-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/button/power-button-basic-config.component.html index 75fcf791b0..a2e3f90aee 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/button/power-button-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/button/power-button-basic-config.component.html @@ -116,6 +116,38 @@ +
    + + {{ 'widgets.power-button.button-icon-on' | translate }} + +
    + + + + + + +
    +
    +
    + + {{ 'widgets.power-button.button-icon-off' | translate }} + +
    + + + + + + +
    +
    {{ 'widgets.power-button.power-on-colors' | translate }}
    diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/button/power-button-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/button/power-button-basic-config.component.ts index 2c92936141..00b0a41e18 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/button/power-button-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/button/power-button-basic-config.component.ts @@ -87,6 +87,19 @@ export class PowerButtonBasicConfigComponent extends BasicWidgetConfigComponent icon: [configData.config.titleIcon, []], iconColor: [configData.config.iconColor, []], + onButtonIcon: this.fb.group({ + showIcon: [settings.onButtonIcon.showIcon, []], + iconSize: [settings.onButtonIcon.iconSize, [Validators.min(0)]], + iconSizeUnit: [settings.onButtonIcon.iconSizeUnit, []], + icon: [settings.onButtonIcon.icon, []], + }), + offButtonIcon: this.fb.group({ + showIcon: [settings.offButtonIcon.showIcon, []], + iconSize: [settings.offButtonIcon.iconSize, [Validators.min(0)]], + iconSizeUnit: [settings.offButtonIcon.iconSizeUnit, []], + icon: [settings.offButtonIcon.icon, []], + }), + mainColorOn: [settings.mainColorOn, []], backgroundColorOn: [settings.backgroundColorOn, []], @@ -128,6 +141,9 @@ export class PowerButtonBasicConfigComponent extends BasicWidgetConfigComponent this.widgetConfig.config.settings.layout = config.layout; + this.widgetConfig.config.settings.onButtonIcon = config.onButtonIcon; + this.widgetConfig.config.settings.offButtonIcon = config.offButtonIcon; + this.widgetConfig.config.settings.mainColorOn = config.mainColorOn; this.widgetConfig.config.settings.backgroundColorOn = config.backgroundColorOn; @@ -148,12 +164,14 @@ export class PowerButtonBasicConfigComponent extends BasicWidgetConfigComponent } protected validatorTriggers(): string[] { - return ['showTitle', 'showIcon']; + return ['showTitle', 'showIcon', 'onButtonIcon.showIcon', 'offButtonIcon.showIcon']; } protected updateValidators(emitEvent: boolean, trigger?: string) { const showTitle: boolean = this.powerButtonWidgetConfigForm.get('showTitle').value; const showIcon: boolean = this.powerButtonWidgetConfigForm.get('showIcon').value; + const onButtonIcon: boolean = this.powerButtonWidgetConfigForm.get('onButtonIcon').get('showIcon').value; + const offButtonIcon: boolean = this.powerButtonWidgetConfigForm.get('offButtonIcon').get('showIcon').value; if (showTitle) { this.powerButtonWidgetConfigForm.get('title').enable(); this.powerButtonWidgetConfigForm.get('titleFont').enable(); @@ -180,6 +198,24 @@ export class PowerButtonBasicConfigComponent extends BasicWidgetConfigComponent this.powerButtonWidgetConfigForm.get('icon').disable(); this.powerButtonWidgetConfigForm.get('iconColor').disable(); } + if (onButtonIcon) { + this.powerButtonWidgetConfigForm.get('onButtonIcon').get('iconSize').enable(); + this.powerButtonWidgetConfigForm.get('onButtonIcon').get('iconSizeUnit').enable(); + this.powerButtonWidgetConfigForm.get('onButtonIcon').get('icon').enable(); + } else { + this.powerButtonWidgetConfigForm.get('onButtonIcon').get('iconSize').disable(); + this.powerButtonWidgetConfigForm.get('onButtonIcon').get('iconSizeUnit').disable(); + this.powerButtonWidgetConfigForm.get('onButtonIcon').get('icon').disable(); + } + if (offButtonIcon) { + this.powerButtonWidgetConfigForm.get('offButtonIcon').get('iconSize').enable(); + this.powerButtonWidgetConfigForm.get('offButtonIcon').get('iconSizeUnit').enable(); + this.powerButtonWidgetConfigForm.get('offButtonIcon').get('icon').enable(); + } else { + this.powerButtonWidgetConfigForm.get('offButtonIcon').get('iconSize').disable(); + this.powerButtonWidgetConfigForm.get('offButtonIcon').get('iconSizeUnit').disable(); + this.powerButtonWidgetConfigForm.get('offButtonIcon').get('icon').disable(); + } } private getCardButtons(config: WidgetConfig): string[] { diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.html index 9808548bbe..a0776f5aa8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.html @@ -63,7 +63,7 @@
    -
    +
    widgets.liquid-level-card.shape
    diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.ts index 3f7baa5d7c..0b06c062ef 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.ts @@ -66,7 +66,7 @@ import { UtilsService } from '@core/services/utils.service'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @Component({ - selector: 'liquid-level-card-basic-config', + selector: 'tb-liquid-level-card-basic-config', templateUrl: './liquid-level-card-basic-config.component.html', styleUrls: ['../basic-config.scss'] }) diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/value-stepper-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/value-stepper-basic-config.component.html new file mode 100644 index 0000000000..748cf64c7f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/value-stepper-basic-config.component.html @@ -0,0 +1,297 @@ + + + +
    +
    widgets.value-stepper.behavior
    +
    +
    widgets.value-stepper.initial-state
    + +
    +
    +
    widgets.value-stepper.left-button-click
    + +
    +
    +
    widgets.value-stepper.right-button-click
    + +
    +
    +
    widgets.button-state.disabled-state
    + +
    +
    +
    +
    widget-config.appearance
    + + + {{ valueStepperTypeTranslationMap.get(type) | translate }} + + +
    + + {{ 'widgets.value-stepper.auto-scale' | translate }} + +
    + +
    +
    {{ 'widgets.value-stepper.value-range' | translate }}
    +
    +
    widgets.value-stepper.min-range
    + + + +
    widgets.value-stepper.max-range
    + + + +
    +
    +
    +
    {{ 'widgets.value-stepper.value-increment-decrement-step' | translate }}
    + + + +
    +
    + + {{ 'widgets.value-stepper.value' | translate }} + +
    + + + +
    widget-config.decimals-suffix
    +
    + + + + +
    +
    + +
    +
    {{ 'widgets.value-stepper.value-box-background' | translate }}
    + + +
    +
    + + {{ 'widgets.value-stepper.border' | translate }} + +
    + + +
    px
    +
    + + +
    +
    +
    +
    +
    +
    widgets.value-stepper.button-appearance
    + + {{ 'widgets.value-stepper.left' | translate }} + {{ 'widgets.value-stepper.right' | translate }} + +
    +
    +
    + + {{ 'widgets.value-stepper.left-button' | translate }} + +
    +
    +
    {{ 'widgets.value-stepper.icon' | translate }}
    +
    + + + + + + +
    +
    +
    +
    {{ 'widgets.value-stepper.button-on-colors' | translate }}
    +
    +
    +
    widgets.value-stepper.main
    + + +
    + +
    +
    widgets.value-stepper.background
    + + +
    +
    +
    +
    +
    {{ 'widgets.value-stepper.disabled-colors' | translate }}
    +
    +
    +
    widgets.value-stepper.main
    + + +
    + +
    +
    widgets.value-stepper.background
    + + +
    +
    +
    +
    +
    +
    + + {{ 'widgets.value-stepper.right-button' | translate }} + +
    +
    +
    {{ 'widgets.value-stepper.icon' | translate }}
    +
    + + + + + + +
    +
    +
    +
    {{ 'widgets.value-stepper.button-on-colors' | translate }}
    +
    +
    +
    widgets.value-stepper.main
    + + +
    + +
    +
    widgets.value-stepper.background
    + + +
    +
    +
    +
    +
    {{ 'widgets.value-stepper.disabled-colors' | translate }}
    +
    +
    +
    widgets.value-stepper.main
    + + +
    + +
    +
    widgets.value-stepper.background
    + + +
    +
    +
    +
    +
    +
    +
    widget-config.card-appearance
    +
    +
    {{ 'widgets.background.background' | translate }}
    + + +
    +
    +
    widget-config.show-card-buttons
    + + {{ 'fullscreen.fullscreen' | translate }} + +
    +
    +
    {{ 'widget-config.card-border-radius' | translate }}
    + + + +
    +
    +
    {{ 'widget-config.card-padding' | translate }}
    + + + +
    +
    + + +
    diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/value-stepper-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/value-stepper-basic-config.component.ts new file mode 100644 index 0000000000..abee223b40 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/value-stepper-basic-config.component.ts @@ -0,0 +1,225 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { BasicWidgetConfigComponent } from '@home/components/widget/config/widget-config.component.models'; +import { WidgetConfigComponentData } from '@home/models/widget-component.models'; +import { TargetDevice, WidgetConfig, } from '@shared/models/widget.models'; +import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; +import { formatValue, isUndefined } from '@core/utils'; +import { ValueType } from '@shared/models/constants'; +import { + valueStepperDefaultSettings, + valueStepperTypeImages, + valueStepperTypes, + valueStepperTypeTranslations, + ValueStepperWidgetSettings +} from '@home/components/widget/lib/rpc/value-stepper-widget.models'; + +type ButtonAppearanceType = 'left' | 'right'; + +@Component({ + selector: 'tb-value-stepper-basic-config', + templateUrl: './value-stepper-basic-config.component.html', + styleUrls: ['../basic-config.scss'] +}) +export class ValueStepperBasicConfigComponent extends BasicWidgetConfigComponent { + + get targetDevice(): TargetDevice { + return this.valueStepperWidgetConfigForm.get('targetDevice').value; + } + + valueStepperTypeTranslationMap = valueStepperTypeTranslations; + valueStepperTypes = valueStepperTypes; + valueStepperTypeImageMap = valueStepperTypeImages; + + buttonAppearanceType: ButtonAppearanceType = 'left'; + + valueType = ValueType; + + valueStepperWidgetConfigForm: UntypedFormGroup; + + valuePreviewFn = this._valuePreviewFn.bind(this); + + constructor(protected store: Store, + protected widgetConfigComponent: WidgetConfigComponent, + private fb: UntypedFormBuilder) { + super(store, widgetConfigComponent); + } + + protected configForm(): UntypedFormGroup { + return this.valueStepperWidgetConfigForm; + } + + protected onConfigSet(configData: WidgetConfigComponentData) { + const settings: ValueStepperWidgetSettings = {...valueStepperDefaultSettings, ...(configData.config.settings || {})}; + this.valueStepperWidgetConfigForm = this.fb.group({ + targetDevice: [configData.config.targetDevice, []], + + initialState: [settings.initialState, []], + leftButtonClick: [settings.leftButtonClick, []], + rightButtonClick: [settings.rightButtonClick, []], + disabledState: [settings.disabledState, []], + + appearance: this.fb.group({ + type: [settings.appearance.type, []], + autoScale: [settings.appearance.autoScale, []], + minValueRange: [settings.appearance.minValueRange, []], + maxValueRange: [settings.appearance.maxValueRange, []], + valueStep: [settings.appearance.valueStep, [Validators.min(0)]], + showValueBox: [settings.appearance.showValueBox, []], + valueUnits: [settings.appearance.valueUnits, []], + valueDecimals: [settings.appearance.valueDecimals, []], + valueFont: [settings.appearance.valueFont, []], + valueColor: [settings.appearance.valueColor], + valueBoxBackground: [settings.appearance.valueBoxBackground, []], + showBorder: [settings.appearance.showBorder, []], + borderWidth: [settings.appearance.borderWidth, []], + borderColor: [settings.appearance.borderColor, []] + }), + + buttonAppearance: this.fb.group({ + leftButton: this.fb.group({ + showButton: [settings.buttonAppearance.leftButton.showButton], + icon: [settings.buttonAppearance.leftButton.icon], + iconSize: [settings.buttonAppearance.leftButton.iconSize], + iconSizeUnit: [settings.buttonAppearance.leftButton.iconSizeUnit], + mainColorOn: [settings.buttonAppearance.leftButton.mainColorOn, []], + backgroundColorOn: [settings.buttonAppearance.leftButton.backgroundColorOn, []], + mainColorDisabled: [settings.buttonAppearance.leftButton.mainColorDisabled, []], + backgroundColorDisabled: [settings.buttonAppearance.leftButton.backgroundColorDisabled, []] + }), + rightButton: this.fb.group({ + showButton: [settings.buttonAppearance.rightButton.showButton], + icon: [settings.buttonAppearance.rightButton.icon], + iconSize: [settings.buttonAppearance.rightButton.iconSize], + iconSizeUnit: [settings.buttonAppearance.rightButton.iconSizeUnit], + mainColorOn: [settings.buttonAppearance.rightButton.mainColorOn, []], + backgroundColorOn: [settings.buttonAppearance.rightButton.backgroundColorOn, []], + mainColorDisabled: [settings.buttonAppearance.rightButton.mainColorDisabled, []], + backgroundColorDisabled: [settings.buttonAppearance.rightButton.backgroundColorDisabled, []] + }) + }), + + background: [settings.background, []], + cardButtons: [this.getCardButtons(configData.config), []], + borderRadius: [configData.config.borderRadius, []], + padding: [settings.padding, []], + + actions: [configData.config.actions || {}, []] + }); + } + + + protected prepareOutputConfig(config: any): WidgetConfigComponentData { + this.widgetConfig.config.targetDevice = config.targetDevice; + this.widgetConfig.config.settings = this.widgetConfig.config.settings || {}; + this.widgetConfig.config.settings.initialState = config.initialState; + this.widgetConfig.config.settings.disabledState = config.disabledState; + this.widgetConfig.config.settings.leftButtonClick = config.leftButtonClick; + this.widgetConfig.config.settings.rightButtonClick = config.rightButtonClick; + this.widgetConfig.config.settings.appearance = config.appearance; + this.widgetConfig.config.settings.buttonAppearance = config.buttonAppearance; + + this.widgetConfig.config.settings.background = config.background; + this.setCardButtons(config.cardButtons, this.widgetConfig.config); + this.widgetConfig.config.borderRadius = config.borderRadius; + this.widgetConfig.config.settings.padding = config.padding; + this.widgetConfig.config.actions = config.actions; + + return this.widgetConfig; + } + + + protected validatorTriggers(): string[] { + return ['appearance.showValueBox', 'appearance.showBorder', + 'buttonAppearance.leftButton.showButton', 'buttonAppearance.rightButton.showButton']; + } + + protected updateValidators(emitEvent: boolean, trigger?: string) { + const showValueBox: boolean = this.valueStepperWidgetConfigForm.get('appearance').get('showValueBox').value; + const showBorder: boolean = this.valueStepperWidgetConfigForm.get('appearance').get('showBorder').value; + const showLeftButton: boolean = this.valueStepperWidgetConfigForm.get('buttonAppearance').get('leftButton').get('showButton').value; + const showRightButton: boolean = this.valueStepperWidgetConfigForm.get('buttonAppearance').get('rightButton').get('showButton').value; + if (showValueBox) { + this.valueStepperWidgetConfigForm.get('appearance').get('valueUnits').enable(); + this.valueStepperWidgetConfigForm.get('appearance').get('valueDecimals').enable(); + this.valueStepperWidgetConfigForm.get('appearance').get('valueFont').enable(); + this.valueStepperWidgetConfigForm.get('appearance').get('valueColor').enable(); + this.valueStepperWidgetConfigForm.get('appearance').get('valueBoxBackground').enable(); + this.valueStepperWidgetConfigForm.get('appearance').get('showBorder').enable({emitEvent: false}); + if (showBorder) { + this.valueStepperWidgetConfigForm.get('appearance').get('borderWidth').enable(); + this.valueStepperWidgetConfigForm.get('appearance').get('borderColor').enable(); + } else { + this.valueStepperWidgetConfigForm.get('appearance').get('borderWidth').disable(); + this.valueStepperWidgetConfigForm.get('appearance').get('borderColor').disable(); + } + } else { + this.valueStepperWidgetConfigForm.get('appearance').get('valueUnits').disable(); + this.valueStepperWidgetConfigForm.get('appearance').get('valueDecimals').disable(); + this.valueStepperWidgetConfigForm.get('appearance').get('valueFont').disable(); + this.valueStepperWidgetConfigForm.get('appearance').get('valueColor').disable(); + this.valueStepperWidgetConfigForm.get('appearance').get('valueBoxBackground').disable(); + this.valueStepperWidgetConfigForm.get('appearance').get('showBorder').disable({emitEvent: false}); + this.valueStepperWidgetConfigForm.get('appearance').get('borderWidth').disable(); + this.valueStepperWidgetConfigForm.get('appearance').get('borderColor').disable(); + } + this.buttonValidators(showLeftButton, 'leftButton'); + this.buttonValidators(showRightButton, 'rightButton'); + } + + private buttonValidators(showButtonValue: boolean, button: string) { + if (showButtonValue) { + this.valueStepperWidgetConfigForm.get('buttonAppearance').get(button).get('icon').enable() + this.valueStepperWidgetConfigForm.get('buttonAppearance').get(button).get('iconSize').enable() + this.valueStepperWidgetConfigForm.get('buttonAppearance').get(button).get('iconSizeUnit').enable() + this.valueStepperWidgetConfigForm.get('buttonAppearance').get(button).get('mainColorOn').enable() + this.valueStepperWidgetConfigForm.get('buttonAppearance').get(button).get('backgroundColorOn').enable() + this.valueStepperWidgetConfigForm.get('buttonAppearance').get(button).get('mainColorDisabled').enable() + this.valueStepperWidgetConfigForm.get('buttonAppearance').get(button).get('backgroundColorDisabled').enable() + } else { + this.valueStepperWidgetConfigForm.get('buttonAppearance').get(button).get('icon').disable() + this.valueStepperWidgetConfigForm.get('buttonAppearance').get(button).get('iconSize').disable() + this.valueStepperWidgetConfigForm.get('buttonAppearance').get(button).get('iconSizeUnit').disable() + this.valueStepperWidgetConfigForm.get('buttonAppearance').get(button).get('mainColorOn').disable() + this.valueStepperWidgetConfigForm.get('buttonAppearance').get(button).get('backgroundColorOn').disable() + this.valueStepperWidgetConfigForm.get('buttonAppearance').get(button).get('mainColorDisabled').disable() + this.valueStepperWidgetConfigForm.get('buttonAppearance').get(button).get('backgroundColorDisabled').disable() + } + } + + private getCardButtons(config: WidgetConfig): string[] { + const buttons: string[] = []; + if (isUndefined(config.enableFullscreen) || config.enableFullscreen) { + buttons.push('fullscreen'); + } + return buttons; + } + + private setCardButtons(buttons: string[], config: WidgetConfig) { + config.enableFullscreen = buttons.includes('fullscreen'); + } + + private _valuePreviewFn(): string { + const units: string = this.valueStepperWidgetConfigForm.get('appearance').get('valueUnits').value; + const decimals: number = this.valueStepperWidgetConfigForm.get('appearance').get('valueDecimals').value; + return formatValue(48, decimals, units, false); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.ts index ab63e0e55d..1f3e9bcbc7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.ts @@ -39,7 +39,6 @@ import { import { Subscription } from 'rxjs'; import { TranslateService } from '@ngx-translate/core'; import { DynamicFormData, IWidgetSettingsComponent, Widget, WidgetSettings } from '@shared/models/widget.models'; -import { widgetSettingsComponentsMap } from '@home/components/widget/lib/settings/widget-settings.module'; import { Dashboard } from '@shared/models/dashboard.models'; import { WidgetService } from '@core/http/widget.service'; import { IAliasController } from '@core/api/widget-api.models'; @@ -215,7 +214,7 @@ export class WidgetSettingsComponent implements ControlValueAccessor, OnDestroy, this.definedSettingsComponent = null; } if (this.settingsDirective && this.settingsDirective.length) { - const componentType = widgetSettingsComponentsMap[this.settingsDirective]; + const componentType = this.widgetService.getWidgetSettingsComponentTypeBySelector(this.settingsDirective); if (!componentType) { this.definedDirectiveError = this.translate.instant('widget-config.settings-component-not-found', {selector: this.settingsDirective}); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.models.ts index b4a46d9592..2e02d36f04 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.models.ts @@ -25,6 +25,7 @@ import { WidgetContext } from '@home/models/widget-component.models'; import { BehaviorSubject, forkJoin, + merge, Observable, Observer, of, @@ -33,7 +34,7 @@ import { switchMap, throwError } from 'rxjs'; -import { catchError, delay, map, share, take } from 'rxjs/operators'; +import { catchError, debounceTime, delay, map, share, take } from 'rxjs/operators'; import { AfterViewInit, ChangeDetectorRef, Directive, Input, OnDestroy, OnInit, TemplateRef } from '@angular/core'; import { DataToValueSettings, @@ -53,10 +54,11 @@ import { import { ValueType } from '@shared/models/constants'; import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models'; import { EntityId } from '@shared/models/id/entity-id'; -import { isDefinedAndNotNull } from '@core/utils'; +import { deepClone, isDefinedAndNotNull } from '@core/utils'; import { parseError } from '@shared/models/error.models'; import { CompiledTbFunction, compileTbFunction } from '@shared/models/js-function.models'; import { HttpClient } from '@angular/common/http'; +import { StateObject } from '@core/api/widget-api.models'; @Directive() // eslint-disable-next-line @angular-eslint/directive-class-suffix @@ -269,6 +271,8 @@ export abstract class ValueGetter extends ValueAction { return new AlarmStatusValueGetter(ctx, settings, valueType, valueObserver, simulated); case GetValueAction.GET_DASHBOARD_STATE: return new DashboardStateGetter(ctx, settings, valueType, valueObserver, simulated); + case GetValueAction.GET_DASHBOARD_STATE_OBJECT: + return new DashboardStateWithParamsGetter(ctx, settings, valueType, valueObserver, simulated); } } @@ -641,6 +645,33 @@ export class DashboardStateGetter extends ValueGetter { } } +export class DashboardStateWithParamsGetter extends ValueGetter { + constructor(protected ctx: WidgetContext, + protected settings: GetValueSettings, + protected valueType: ValueType, + protected valueObserver: Partial>, + protected simulated: boolean) { + super(ctx, settings, valueType, valueObserver, simulated); + } + + protected doGetValue(): Observable { + if (this.simulated) { + return of({id: 'default', params: {}}); + } else { + return merge( + this.ctx.stateController.dashboardCtrl.dashboardCtx.stateId, + this.ctx.stateController.dashboardCtrl.dashboardCtx.stateChanged + ).pipe( + debounceTime(10), + map(() => ({ + id: this.ctx.stateController.getStateId(), + params: deepClone(this.ctx.stateController.getStateParams()) + })) + ); + } + } +} + export class ExecuteRpcValueSetter extends ValueSetter { private readonly executeRpcSettings: RpcSettings; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts index c648dceb2a..1f4a07907e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts @@ -109,7 +109,7 @@ export const renderTimeSeriesBar = (params: CustomSeriesRenderItemParams, api: C if (offset !== 0 && isNumeric(value)) { lowerLeft = api.coord([startTime, Number(value) >= 0 ? Number(value) + offset : offset]); } else { - lowerLeft = api.coord([startTime, value]); + lowerLeft = api.coord([startTime, Number(value) >= 0 ? Number(value) : 0]); } const size = api.size([delta, value]); const width = size[0]; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.scss index 1a0ce86b89..1f92b714a9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.scss @@ -15,7 +15,6 @@ */ :host { width: 100%; - height: 100%; min-width: 300px; overflow: auto; background: #fff; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-link.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-link.component.html index 4db770a919..dccfec03c2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-link.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-link.component.html @@ -49,7 +49,7 @@ - +
    diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-links-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-links-widget.component.html index 474824cb12..b1cb50d51b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-links-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-links-widget.component.html @@ -34,7 +34,7 @@ - +
    diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.html index 004d9807b7..c0f980ca2e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.html @@ -26,7 +26,7 @@ -
    +
    widgets.getting-started.sys-admin.step1.title
    {{ 'tenant.tenants' | translate }}
    @@ -37,7 +37,7 @@ -
    +
    widgets.getting-started.sys-admin.step2.title
    {{ 'admin.settings' | translate }}
    @@ -48,7 +48,7 @@ -
    +
    widgets.getting-started.sys-admin.step3.title
    {{ 'admin.settings' | translate }}
    @@ -59,7 +59,7 @@ -
    +
    widgets.getting-started.sys-admin.step4.title
    {{ 'admin.settings' | translate }}
    @@ -70,7 +70,7 @@ -
    +
    widgets.getting-started.sys-admin.step5.title
    {{ 'admin.settings' | translate }}
    @@ -81,7 +81,7 @@ -
    +
    widgets.getting-started.sys-admin.step6.title
    {{ 'admin.settings' | translate }}
    @@ -94,7 +94,7 @@ -
    +
    widgets.getting-started.tenant-admin.step1.title
    {{ 'device.devices' | translate }}
    @@ -105,7 +105,7 @@ -
    +
    widgets.getting-started.tenant-admin.step2.title
    @@ -153,7 +153,7 @@ -
    +
    widgets.getting-started.tenant-admin.step3.title
    {{ 'dashboard.dashboards' | translate }}
    @@ -164,7 +164,7 @@ -
    +
    widgets.getting-started.tenant-admin.step4.title
    {{ 'widgets.getting-started.tenant-admin.step4.alarm-rules' | translate }}
    @@ -175,7 +175,7 @@ -
    +
    widgets.getting-started.tenant-admin.step5.title
    @@ -193,7 +193,7 @@ -
    +
    widgets.getting-started.tenant-admin.step6.title
    {{ 'customer.customers' | translate }}
    @@ -205,7 +205,7 @@ -
    +
    diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.scss index e684c094d7..4a21006cc0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.scss @@ -38,6 +38,9 @@ .tb-get-started-container { flex: 1; overflow: auto; + mat-stepper { + transform: scale(1); //fixed blur content + } } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/common-maps-utils.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/common-maps-utils.ts index cd974ee22c..2d466803eb 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/common-maps-utils.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/common-maps-utils.ts @@ -260,7 +260,7 @@ export const parseWithTranslation = { export function functionValueCalculator(useFunction: boolean, func: CompiledTbFunction, params = [], defaultValue: T): T { let res: T; - if (useFunction && isDefined(func) && isFunction(func)) { + if (useFunction && isDefinedAndNotNull(func)) { try { res = func.execute(...params); if (!isDefinedAndNotNull(res) || res === '') { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts index ee5af0bfb5..f74acd9f2c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts @@ -357,7 +357,7 @@ export default abstract class LeafletMap { this.map.pm.Toolbar.copyDrawControl('Circle', { name: 'tbCircle', afterClick: () => this.selectEntityWithoutLocation('tbCircle'), - disabled: true, + disabled: false, actions }); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/polyline.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/polyline.ts index 347782d210..46466985a3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/polyline.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/polyline.ts @@ -15,8 +15,8 @@ /// // @ts-ignore -import L, { PolylineDecorator, PolylineDecoratorOptions, Symbol } from 'leaflet'; import 'leaflet-polylinedecorator'; +import L, { PolylineDecorator, PolylineDecoratorOptions, Symbol } from 'leaflet'; import { WidgetPolylineSettings } from './map-models'; import { functionValueCalculator } from '@home/components/widget/lib/maps/common-maps-utils'; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html index 51d4ec22b1..3e1ca011fd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html @@ -138,6 +138,28 @@ {{ getErrorMessageText(key.settings, 'required') }} + +
    + {{key.label}} + + + + + + + {{ getCustomTranslationText(option.label ? option.label : option.value) }} + + + +
    diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss index 4c8ffea030..b5e83a4925 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss @@ -75,7 +75,7 @@ :host ::ng-deep { .tb-multiple-input { - .mat-mdc-slide-toggle, .mat-mdc-checkbox { + .mat-mdc-slide-toggle, .mat-mdc-checkbox, .mat-mdc-radio-button { .mdc-form-field { width: 100%; & > label { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts index 7a80f4904a..94b33eb9d0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts @@ -54,7 +54,7 @@ type FieldAlignment = 'row' | 'column'; type MultipleInputWidgetDataKeyType = 'server' | 'shared' | 'timeseries'; export type MultipleInputWidgetDataKeyValueType = 'string' | 'double' | 'integer' | 'JSON' | 'booleanCheckbox' | 'booleanSwitch' | - 'dateTime' | 'date' | 'time' | 'select' | 'color'; + 'dateTime' | 'date' | 'time' | 'select' | 'radio' | 'color'; export type MultipleInputWidgetDataKeyEditableType = 'editable' | 'disabled' | 'readonly'; type ConvertGetValueFunction = (value: any, ctx: WidgetContext) => any; @@ -86,6 +86,9 @@ interface MultipleInputWidgetDataKeySettings { dataKeyValueType: MultipleInputWidgetDataKeyValueType; slideToggleLabelPosition?: 'after' | 'before'; selectOptions: MultipleInputWidgetSelectOption[]; + radioColor: string; + radioColumns: number; + radioLabelPosition?: 'after' | 'before'; required: boolean; isEditable: MultipleInputWidgetDataKeyEditableType; disabledOnDataKey: string; @@ -300,7 +303,7 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni // For backward compatibility - if (dataKey.settings.dataKeyValueType === 'select') { + if (dataKey.settings.dataKeyValueType === 'select' || dataKey.settings.dataKeyValueType === 'radio') { dataKey.settings.selectOptions.forEach((option) => { if (option.value.toLowerCase() === 'null') { option.value = null; @@ -444,6 +447,7 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni } break; case 'select': + case 'radio': value = keyValue !== null ? keyValue.toString() : null; break; case 'JSON': @@ -566,6 +570,12 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni return this.getTranslatedErrorText(errorMessage, defaultMessage, messageValues); } + public radioButtonSelectedColor(radioColor: string) { + if (isDefinedAndNotNull(radioColor)) { + return `--mdc-radio-selected-icon-color: ${radioColor}; --mdc-radio-selected-focus-icon-color: ${radioColor}; --mdc-radio-selected-hover-icon-color: ${radioColor}; --mdc-radio-selected-pressed-icon-color: ${radioColor}; --mat-radio-checked-ripple-color: ${radioColor};` + } + } + public getTranslatedErrorText(errorMessage: string, defaultMessage: string, messageValues?: object): string { let messageText; if (errorMessage && errorMessage.length) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.ts index 6c9072e915..cab9b9d572 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.ts @@ -18,7 +18,8 @@ import { AfterViewInit, ChangeDetectorRef, Component, - ElementRef, NgZone, + ElementRef, + NgZone, OnDestroy, OnInit, Renderer2, @@ -38,6 +39,7 @@ import { PowerButtonWidgetSettings } from '@home/components/widget/lib/rpc/power-button-widget.models'; import { SVG, Svg } from '@svgdotjs/svg.js'; +import { MatIconRegistry } from '@angular/material/icon'; @Component({ selector: 'tb-power-button-widget', @@ -72,6 +74,7 @@ export class PowerButtonWidgetComponent extends constructor(protected imagePipe: ImagePipe, protected sanitizer: DomSanitizer, private renderer: Renderer2, + private iconRegistry: MatIconRegistry, protected cd: ChangeDetectorRef, protected zone: NgZone) { super(cd); @@ -180,7 +183,7 @@ export class PowerButtonWidgetComponent extends this.renderer.setStyle(this.svgShape.node, 'user-select', 'none'); this.zone.run(() => { - this.powerButtonSvgShape = PowerButtonShape.fromSettings(this.ctx, this.svgShape, + this.powerButtonSvgShape = PowerButtonShape.fromSettings(this.ctx, this.svgShape, this.iconRegistry, this.settings, this.value, this.disabledState, () => this.onClick()); }); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.models.ts index 503347939d..540ef8f9f3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.models.ts @@ -28,6 +28,11 @@ import { Circle, Effect, Element, G, Gradient, Path, Runner, Svg, Text, Timeline import '@svgdotjs/svg.filter.js'; import tinycolor from 'tinycolor2'; import { WidgetContext } from '@home/models/widget-component.models'; +import { Observable, of } from 'rxjs'; +import { isSvgIcon, splitIconName } from '@shared/models/icon.models'; +import { catchError, map, take } from 'rxjs/operators'; +import { MatIconRegistry } from '@angular/material/icon'; +import { isDefinedAndNotNull } from '@core/utils'; export enum PowerButtonLayout { default = 'default', @@ -71,20 +76,29 @@ export const powerButtonLayoutImages = new Map( ] ); +export interface ButtonIconSettings { + showIcon: boolean; + iconSize: number; + iconSizeUnit: string; + icon: string; +} + export interface PowerButtonWidgetSettings { - initialState: GetValueSettings; - disabledState: GetValueSettings; - onUpdateState: SetValueSettings; - offUpdateState: SetValueSettings; + initialState?: GetValueSettings; + disabledState?: GetValueSettings; + onUpdateState?: SetValueSettings; + offUpdateState?: SetValueSettings; layout: PowerButtonLayout; + onButtonIcon: ButtonIconSettings, + offButtonIcon: ButtonIconSettings, mainColorOn: string; backgroundColorOn: string; mainColorOff: string; backgroundColorOff: string; mainColorDisabled: string; backgroundColorDisabled: string; - background: BackgroundSettings; - padding: string; + background?: BackgroundSettings; + padding?: string; } export const powerButtonDefaultSettings: PowerButtonWidgetSettings = { @@ -177,6 +191,18 @@ export const powerButtonDefaultSettings: PowerButtonWidgetSettings = { } }, layout: PowerButtonLayout.default, + onButtonIcon: { + showIcon: false, + iconSize: 32, + iconSizeUnit: 'px', + icon: 'power_settings_new' + }, + offButtonIcon: { + showIcon: false, + iconSize: 32, + iconSizeUnit: 'px', + icon: 'power_settings_new' + }, mainColorOn: '#3F52DD', backgroundColorOn: '#FFFFFF', mainColorOff: '#A2A2A2', @@ -207,6 +233,11 @@ interface PowerButtonColorState { backgroundColor: PowerButtonColor; } +interface ButtonsIconSettings { + onButtonIcon: ButtonIconSettings; + offButtonIcon: ButtonIconSettings; +} + type PowerButtonShapeColors = Record; const createPowerButtonShapeColors = (settings: PowerButtonWidgetSettings): PowerButtonShapeColors => { @@ -257,33 +288,35 @@ export abstract class PowerButtonShape { static fromSettings(ctx: WidgetContext, svgShape: Svg, + iconRegistry: MatIconRegistry, settings: PowerButtonWidgetSettings, value: boolean, disabled: boolean, onClick: () => void): PowerButtonShape { switch (settings.layout) { case PowerButtonLayout.default: - return new DefaultPowerButtonShape(ctx, svgShape, settings, value, disabled, onClick); + return new DefaultPowerButtonShape(ctx, svgShape, iconRegistry, settings, value, disabled, onClick); case PowerButtonLayout.simplified: - return new SimplifiedPowerButtonShape(ctx, svgShape, settings, value, disabled, onClick); + return new SimplifiedPowerButtonShape(ctx, svgShape, iconRegistry, settings, value, disabled, onClick); case PowerButtonLayout.outlined: - return new OutlinedPowerButtonShape(ctx, svgShape, settings, value, disabled, onClick); + return new OutlinedPowerButtonShape(ctx, svgShape, iconRegistry, settings, value, disabled, onClick); case PowerButtonLayout.default_volume: - return new DefaultVolumePowerButtonShape(ctx, svgShape, settings, value, disabled, onClick); + return new DefaultVolumePowerButtonShape(ctx, svgShape, iconRegistry, settings, value, disabled, onClick); case PowerButtonLayout.simplified_volume: - return new SimplifiedVolumePowerButtonShape(ctx, svgShape, settings, value, disabled, onClick); + return new SimplifiedVolumePowerButtonShape(ctx, svgShape, iconRegistry, settings, value, disabled, onClick); case PowerButtonLayout.outlined_volume: - return new OutlinedVolumePowerButtonShape(ctx, svgShape, settings, value, disabled, onClick); + return new OutlinedVolumePowerButtonShape(ctx, svgShape, iconRegistry, settings, value, disabled, onClick); case PowerButtonLayout.default_icon: - return new DefaultIconPowerButtonShape(ctx, svgShape, settings, value, disabled, onClick); + return new DefaultIconPowerButtonShape(ctx, svgShape, iconRegistry, settings, value, disabled, onClick); case PowerButtonLayout.simplified_icon: - return new SimplifiedIconPowerButtonShape(ctx, svgShape, settings, value, disabled, onClick); + return new SimplifiedIconPowerButtonShape(ctx, svgShape, iconRegistry, settings, value, disabled, onClick); case PowerButtonLayout.outlined_icon: - return new OutlinedIconPowerButtonShape(ctx, svgShape, settings, value, disabled, onClick); + return new OutlinedIconPowerButtonShape(ctx, svgShape, iconRegistry, settings, value, disabled, onClick); } } protected readonly colors: PowerButtonShapeColors; + protected readonly icons: ButtonsIconSettings; protected readonly onLabel: string; protected readonly offLabel: string; @@ -293,18 +326,67 @@ export abstract class PowerButtonShape { protected pressed = false; protected forcePressed = false; + protected offPowerSymbolIcon: Element; + protected onPowerSymbolIcon: Element; + protected offLabelShape: Text; + protected onLabelShape: Text; + + protected offPowerSymbolCircle: Path; + protected offPowerSymbolLine: Path; + protected onPowerSymbolCircle: Path; + protected onPowerSymbolLine: Path; + protected constructor(protected widgetContext: WidgetContext, protected svgShape: Svg, + protected iconRegistry: MatIconRegistry, protected settings: PowerButtonWidgetSettings, protected value: boolean, protected disabled: boolean, protected onClick: () => void) { this.colors = createPowerButtonShapeColors(this.settings); + this.icons = {onButtonIcon: this.settings.onButtonIcon, offButtonIcon: this.settings.offButtonIcon}; this.onLabel = this.widgetContext.translate.instant('widgets.power-button.on-label').toUpperCase(); this.offLabel = this.widgetContext.translate.instant('widgets.power-button.off-label').toUpperCase(); this._drawShape(); } + public createIconElement(icon: string, size: number): Observable { + const isSvg = isSvgIcon(icon); + if (isSvg) { + const [namespace, iconName] = splitIconName(icon); + return this.iconRegistry + .getNamedSvgIcon(iconName, namespace) + .pipe( + take(1), + map((svgElement) => { + const element = new Element(svgElement.firstChild); + const box = element.bbox(); + const scale = size / box.height; + element.scale(scale); + return element; + }), + catchError(() => of(null) + )); + } else { + const iconName = splitIconName(icon)[1]; + const textElement = this.svgShape.text(iconName); + const fontSetClasses = ( + this.iconRegistry.getDefaultFontSetClass() + ).filter(className => className.length > 0); + fontSetClasses.forEach(className => textElement.addClass(className)); + textElement.font({size: `${size}px`}); + textElement.attr({ + style: `font-size: ${size}px`, + 'text-anchor': 'start' + }); + const tspan = textElement.first(); + tspan.attr({ + 'dominant-baseline': 'hanging' + }); + return of(textElement); + } + } + public setValue(value: boolean) { if (this.value !== value) { this.value = value; @@ -330,6 +412,106 @@ export abstract class PowerButtonShape { } } + public drawOffShape(centerGroup: G, label: boolean, labelWeight?: string, circleStroke?: boolean) { + if (this.icons.offButtonIcon.showIcon) { + this.createIconElement(this.icons.offButtonIcon.icon, this.icons.offButtonIcon.iconSize).subscribe(icon => + this.offPowerSymbolIcon = icon.center(cx, cy).addTo(centerGroup)); + } else { + if (label) { + this.offLabelShape = this.createOffLabel(labelWeight).addTo(centerGroup); + } else { + this.offPowerSymbolCircle = this.svgShape.path(circleStroke ? powerCircle : powerCircleStroke).center(cx, cy).addTo(centerGroup); + this.offPowerSymbolLine = this.svgShape.path(circleStroke ? powerLine : powerLineStroke).center(cx, cy-12).addTo(centerGroup); + } + } + } + + public drawOnShape(onCenterGroup?: G, label?: boolean, labelWeight?: string, circleStroke?: boolean, mask?: Circle) { + if (this.icons.onButtonIcon.showIcon) { + this.createIconElement(this.icons.onButtonIcon.icon, this.icons.onButtonIcon.iconSize).subscribe(icon => { + this.onPowerSymbolIcon = icon.center(cx, cy); + if (isDefinedAndNotNull(onCenterGroup)) { + this.onPowerSymbolIcon.addTo(onCenterGroup); + } + if (isDefinedAndNotNull(mask)) { + this.createMask(mask, [this.onPowerSymbolIcon]); + } + }); + } else { + if (label) { + this.onLabelShape = this.createOnLabel(labelWeight); + if (isDefinedAndNotNull(onCenterGroup)) { + this.onLabelShape.addTo(onCenterGroup); + } + if (isDefinedAndNotNull(mask)) { + this.createMask(mask, [this.onLabelShape]); + } + } else { + this.onPowerSymbolCircle = this.svgShape.path(circleStroke ? powerCircle : powerCircleStroke).center(cx, cy); + this.onPowerSymbolLine = this.svgShape.path(circleStroke ? powerLine : powerLineStroke).center(cx, cy-12); + if (isDefinedAndNotNull(onCenterGroup)) { + this.onPowerSymbolCircle.addTo(onCenterGroup); + this.onPowerSymbolLine.addTo(onCenterGroup); + } + if (isDefinedAndNotNull(mask)) { + this.createMask(mask, [this.onPowerSymbolCircle, this.onPowerSymbolLine]); + } + } + } + } + + public onCenterTimeLine(timeline: Timeline, label: boolean) { + if (this.icons.onButtonIcon.showIcon) { + this.onPowerSymbolIcon.timeline(timeline); + } else { + if (label) { + this.onLabelShape.timeline(timeline); + } else { + this.onPowerSymbolCircle.timeline(timeline); + this.onPowerSymbolLine.timeline(timeline); + } + } + } + + public offCenterColor(mainColor: PowerButtonColor, label: boolean) { + if (this.icons.offButtonIcon.showIcon) { + this.offPowerSymbolIcon.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + } else { + if (label) { + this.offLabelShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + } else { + this.offPowerSymbolCircle.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + this.offPowerSymbolLine.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + } + } + } + + public onCenterColor(mainColor: PowerButtonColor, label: boolean) { + if (this.icons.onButtonIcon.showIcon) { + this.onPowerSymbolIcon.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + } else { + if (label) { + this.onLabelShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + } else { + this.onPowerSymbolCircle.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + this.onPowerSymbolLine.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + } + } + } + + public buttonAnimation(scale: number, label: boolean) { + if (this.icons.onButtonIcon.showIcon) { + powerButtonAnimation(this.onPowerSymbolIcon).transform({scale}); + } else { + if (label) { + powerButtonAnimation(this.onLabelShape).transform({scale, origin: {x: cx, y: cy}}); + } else { + powerButtonAnimation(this.onPowerSymbolCircle).transform({scale, origin: {x: cx, y: cy}}); + powerButtonAnimation(this.onPowerSymbolLine).transform({scale, origin: {x: cx, y: cy}}); + } + } + } + private _drawShape() { this.backgroundShape = this.svgShape.circle(powerButtonShapeSize).center(cx, cy) @@ -530,9 +712,7 @@ class DefaultPowerButtonShape extends PowerButtonShape { private outerBorder: Circle; private outerBorderMask: Circle; - private offLabelShape: Text; private onCircleShape: Circle; - private onLabelShape: Text; private pressedShadow: InnerShadowCircle; private pressedTimeline: Timeline; private centerGroup: G; @@ -543,22 +723,20 @@ class DefaultPowerButtonShape extends PowerButtonShape { this.outerBorderMask = this.svgShape.circle(powerButtonShapeSize - 20).center(cx, cy); this.createMask(this.outerBorder, [this.outerBorderMask]); this.centerGroup = this.svgShape.group(); - this.offLabelShape = this.createOffLabel().addTo(this.centerGroup); - this.onCircleShape = this.svgShape.circle(powerButtonShapeSize - 20) - .center(cx, cy); - this.onLabelShape = this.createOnLabel(); - this.createMask(this.onCircleShape, [this.onLabelShape]); + this.drawOffShape(this.centerGroup, true); + this.onCircleShape = this.svgShape.circle(powerButtonShapeSize - 20).center(cx, cy); + this.drawOnShape(null, true, '', false, this.onCircleShape); this.pressedShadow = new InnerShadowCircle(this.svgShape, powerButtonShapeSize - 20, cx, cy, 0, 0); this.pressedTimeline = new Timeline(); this.centerGroup.timeline(this.pressedTimeline); - this.onLabelShape.timeline(this.pressedTimeline); + this.onCenterTimeLine(this.pressedTimeline, true); this.pressedShadow.timeline(this.pressedTimeline); } protected drawColorState(mainColor: PowerButtonColor) { this.outerBorder.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); - this.offLabelShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + this.offCenterColor(mainColor, true); this.onCircleShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); } @@ -578,14 +756,14 @@ class DefaultPowerButtonShape extends PowerButtonShape { this.pressedTimeline.finish(); const pressedScale = 0.75; powerButtonAnimation(this.centerGroup).transform({scale: pressedScale}); - powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale, origin: {x: cx, y: cy}}); + this.buttonAnimation(pressedScale, true); this.pressedShadow.animate(6, 0.6); } protected onPressEnd() { this.pressedTimeline.finish(); powerButtonAnimation(this.centerGroup).transform({scale: 1}); - powerButtonAnimation(this.onLabelShape).transform({scale: 1, origin: {x: cx, y: cy}}); + this.buttonAnimation(1, true); this.pressedShadow.animateRestore(); } @@ -596,8 +774,6 @@ class SimplifiedPowerButtonShape extends PowerButtonShape { private outerBorder: Circle; private outerBorderMask: Circle; private onCircleShape: Circle; - private offLabelShape: Text; - private onLabelShape: Text; private pressedShadow: InnerShadowCircle; private pressedTimeline: Timeline; private centerGroup: G; @@ -608,22 +784,21 @@ class SimplifiedPowerButtonShape extends PowerButtonShape { this.outerBorderMask = this.svgShape.circle(powerButtonShapeSize - 4).center(cx, cy); this.createMask(this.outerBorder, [this.outerBorderMask]); this.centerGroup = this.svgShape.group(); - this.offLabelShape = this.createOffLabel().addTo(this.centerGroup); + this.drawOffShape(this.centerGroup, true); this.onCircleShape = this.svgShape.circle(powerButtonShapeSize).center(cx, cy); - this.onLabelShape = this.createOnLabel(); - this.createMask(this.onCircleShape, [this.onLabelShape]); + this.drawOnShape(null, true, '', false, this.onCircleShape) this.pressedShadow = new InnerShadowCircle(this.svgShape, powerButtonShapeSize - 4, cx, cy, 0, 0); this.pressedTimeline = new Timeline(); this.centerGroup.timeline(this.pressedTimeline); - this.onLabelShape.timeline(this.pressedTimeline); + this.onCenterTimeLine(this.pressedTimeline, true); this.pressedShadow.timeline(this.pressedTimeline); } protected drawColorState(mainColor: PowerButtonColor) { this.outerBorder.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); this.onCircleShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); - this.offLabelShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + this.offCenterColor(mainColor, true); } protected drawOff() { @@ -642,14 +817,14 @@ class SimplifiedPowerButtonShape extends PowerButtonShape { this.pressedTimeline.finish(); const pressedScale = 0.75; powerButtonAnimation(this.centerGroup).transform({scale: pressedScale}); - powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale, origin: {x: cx, y: cy}}); + this.buttonAnimation(pressedScale, true); this.pressedShadow.animate(6, 0.6); } protected onPressEnd() { this.pressedTimeline.finish(); powerButtonAnimation(this.centerGroup).transform({scale: 1}); - powerButtonAnimation(this.onLabelShape).transform({scale: 1, origin: {x: cx, y: cy}}); + this.buttonAnimation(1, true); this.pressedShadow.animateRestore(); } } @@ -659,9 +834,7 @@ class OutlinedPowerButtonShape extends PowerButtonShape { private outerBorderMask: Circle; private innerBorder: Circle; private innerBorderMask: Circle; - private offLabelShape: Text; private onCircleShape: Circle; - private onLabelShape: Text; private pressedShadow: InnerShadowCircle; private pressedTimeline: Timeline; private centerGroup: G; @@ -677,25 +850,23 @@ class OutlinedPowerButtonShape extends PowerButtonShape { this.innerBorderMask = this.svgShape.circle(powerButtonShapeSize - 24).center(cx, cy); this.createMask(this.innerBorder, [this.innerBorderMask]); this.centerGroup = this.svgShape.group(); - this.offLabelShape = this.createOffLabel().addTo(this.centerGroup); + this.drawOffShape(this.centerGroup, true); this.onCenterGroup = this.svgShape.group(); - this.onCircleShape = this.svgShape.circle(powerButtonShapeSize - 28).center(cx, cy) - .addTo(this.onCenterGroup); - this.onLabelShape = this.createOnLabel(); - this.createMask(this.onCircleShape, [this.onLabelShape]); + this.onCircleShape = this.svgShape.circle(powerButtonShapeSize - 28).center(cx, cy).addTo(this.onCenterGroup); + this.drawOnShape(null, true, '', false, this.onCircleShape) this.pressedShadow = new InnerShadowCircle(this.svgShape, powerButtonShapeSize - 24, cx, cy, 0, 0); this.pressedTimeline = new Timeline(); this.centerGroup.timeline(this.pressedTimeline); this.onCenterGroup.timeline(this.pressedTimeline); - this.onLabelShape.timeline(this.pressedTimeline); + this.onCenterTimeLine(this.pressedTimeline, true); this.pressedShadow.timeline(this.pressedTimeline); } protected drawColorState(mainColor: PowerButtonColor) { this.outerBorder.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); this.innerBorder.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); - this.offLabelShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + this.offCenterColor(mainColor, true); this.onCircleShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); } @@ -714,7 +885,7 @@ class OutlinedPowerButtonShape extends PowerButtonShape { const pressedScale = 0.75; powerButtonAnimation(this.centerGroup).transform({scale: pressedScale}); powerButtonAnimation(this.onCenterGroup).transform({scale: 0.98}); - powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale / 0.98, origin: {x: cx, y: cy}}); + this.buttonAnimation(pressedScale / 0.98, true); this.pressedShadow.animate(6, 0.6); } @@ -722,7 +893,7 @@ class OutlinedPowerButtonShape extends PowerButtonShape { this.pressedTimeline.finish(); powerButtonAnimation(this.centerGroup).transform({scale: 1}); powerButtonAnimation(this.onCenterGroup).transform({scale: 1}); - powerButtonAnimation(this.onLabelShape).transform({scale: 1, origin: {x: cx, y: cy}}); + this.buttonAnimation(1, true); this.pressedShadow.animateRestore(); } } @@ -737,35 +908,29 @@ class DefaultVolumePowerButtonShape extends PowerButtonShape { private innerShadow: InnerShadowCircle; //private innerShadowGradient: Gradient; //private innerShadowGradientStop: Stop; - private offLabelShape: Text; - private onCircleShape: Circle; - private onLabelShape: Text; + protected onCircleShape: Circle; private pressedTimeline: Timeline; private centerGroup: G; protected drawOffCenter(centerGroup: G) { - this.offLabelShape = this.createOffLabel('400').addTo(centerGroup); + this.drawOffShape(centerGroup, true, '400'); } protected drawOnCenter() { - this.onLabelShape = this.createOnLabel('400'); - } - - protected addOnCenterToMask(onCircleShape: Circle) { - this.createMask(onCircleShape,[this.onLabelShape]); + this.drawOnShape(null, true, '400', false, this.onCircleShape); } protected addOnCenterTimeLine(pressedTimeline: Timeline) { - this.onLabelShape.timeline(pressedTimeline); + this.onCenterTimeLine(pressedTimeline, true); } protected drawOffCenterColor(mainColor: PowerButtonColor) { - this.offLabelShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + this.offCenterColor(mainColor, true); } protected onCenterAnimation(scale: number) { - powerButtonAnimation(this.onLabelShape).transform({scale, origin: {x: cx, y: cy}}); + this.buttonAnimation(scale, true); } protected drawShape() { @@ -789,7 +954,6 @@ class DefaultVolumePowerButtonShape extends PowerButtonShape { this.drawOffCenter(this.centerGroup); this.onCircleShape = this.svgShape.circle(powerButtonShapeSize - 24).center(cx, cy); this.drawOnCenter(); - this.addOnCenterToMask(this.onCircleShape); this.innerShadow = new InnerShadowCircle(this.svgShape, powerButtonShapeSize - 24, cx, cy, 3, 0.3); this.pressedTimeline = new Timeline(); @@ -857,38 +1021,25 @@ class DefaultVolumePowerButtonShape extends PowerButtonShape { } class DefaultIconPowerButtonShape extends DefaultVolumePowerButtonShape { - private offPowerSymbolCircle: Path; - private offPowerSymbolLine: Path; - private onPowerSymbolCircle: Path; - private onPowerSymbolLine: Path; protected drawOffCenter(centerGroup: G) { - this.offPowerSymbolCircle = this.svgShape.path(powerCircle).center(cx, cy).addTo(centerGroup); - this.offPowerSymbolLine = this.svgShape.path(powerLine).center(cx, cy-12).addTo(centerGroup); + this.drawOffShape(centerGroup, false); } protected drawOnCenter() { - this.onPowerSymbolCircle = this.svgShape.path(powerCircle).center(cx, cy); - this.onPowerSymbolLine = this.svgShape.path(powerLine).center(cx, cy-12); - } - - protected addOnCenterToMask(onCircleShape: Circle) { - this.createMask(onCircleShape, [this.onPowerSymbolCircle, this.onPowerSymbolLine]); + this.drawOnShape(null, false, '', false, this.onCircleShape); } protected addOnCenterTimeLine(pressedTimeline: Timeline) { - this.onPowerSymbolCircle.timeline(pressedTimeline); - this.onPowerSymbolLine.timeline(pressedTimeline); + this.onCenterTimeLine(pressedTimeline, false); } protected drawOffCenterColor(mainColor: PowerButtonColor) { - this.offPowerSymbolCircle.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); - this.offPowerSymbolLine.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + this.offCenterColor(mainColor, false); } protected onCenterAnimation(scale: number) { - powerButtonAnimation(this.onPowerSymbolCircle).transform({scale, origin: {x: cx, y: cy}}); - powerButtonAnimation(this.onPowerSymbolLine).transform({scale, origin: {x: cx, y: cy}}); + this.buttonAnimation(scale, false); } } @@ -896,8 +1047,6 @@ class SimplifiedVolumePowerButtonShape extends PowerButtonShape { private outerBorder: Circle; private outerBorderMask: Circle; - private offLabelShape: Text; - private onLabelShape: Text; private innerShadow: InnerShadowCircle; private pressedShadow: InnerShadowCircle; private pressedTimeline: Timeline; @@ -905,8 +1054,8 @@ class SimplifiedVolumePowerButtonShape extends PowerButtonShape { private onCenterGroup: G; protected drawCenterGroup(centerGroup: G, onCenterGroup: G) { - this.offLabelShape = this.createOffLabel().addTo(centerGroup); - this.onLabelShape = this.createOnLabel().addTo(onCenterGroup); + this.drawOffShape(centerGroup, true); + this.drawOnShape(onCenterGroup, true); } protected drawShape() { @@ -926,8 +1075,8 @@ class SimplifiedVolumePowerButtonShape extends PowerButtonShape { } protected drawColorState(mainColor: PowerButtonColor){ - this.offLabelShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); - this.onLabelShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + this.offCenterColor(mainColor, true); + this.onCenterColor(mainColor, true); } protected drawOff() { @@ -970,23 +1119,15 @@ class SimplifiedVolumePowerButtonShape extends PowerButtonShape { } class SimplifiedIconPowerButtonShape extends SimplifiedVolumePowerButtonShape { - private offPowerSymbolCircle: Path; - private offPowerSymbolLine: Path; - private onPowerSymbolCircle: Path; - private onPowerSymbolLine: Path; protected drawCenterGroup(centerGroup: G, onCenterGroup: G) { - this.offPowerSymbolCircle = this.svgShape.path(powerCircle).center(cx, cy).addTo(centerGroup); - this.offPowerSymbolLine = this.svgShape.path(powerLine).center(cx, cy-12).addTo(centerGroup); - this.onPowerSymbolCircle = this.svgShape.path(powerCircle).center(cx, cy).addTo(onCenterGroup); - this.onPowerSymbolLine = this.svgShape.path(powerLine).center(cx, cy-12).addTo(onCenterGroup); + this.drawOffShape(centerGroup, false); + this.drawOnShape(onCenterGroup, false, '', false); } protected drawColorState(mainColor: PowerButtonColor) { - this.offPowerSymbolCircle.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); - this.offPowerSymbolLine.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); - this.onPowerSymbolCircle.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); - this.onPowerSymbolLine.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + this.offCenterColor(mainColor, false); + this.onCenterColor(mainColor, false); } } @@ -996,36 +1137,30 @@ class OutlinedVolumePowerButtonShape extends PowerButtonShape { private outerBorderGradient: Gradient; private innerBorder: Circle; private innerBorderMask: Circle; - private offLabelShape: Text; - private onCircleShape: Circle; - private onLabelShape: Text; + protected onCircleShape: Circle; private pressedShadow: InnerShadowCircle; private pressedTimeline: Timeline; private centerGroup: G; private onCenterGroup: G; protected drawOffCenter(centerGroup: G) { - this.offLabelShape = this.createOffLabel('800').addTo(centerGroup); + this.drawOffShape(centerGroup, true, '800'); } protected drawOnCenter() { - this.onLabelShape = this.createOnLabel('800'); - } - - protected addOnCenterToMask(onCircleShape: Circle) { - this.createMask(onCircleShape,[this.onLabelShape]); + this.drawOnShape(null, true, '800', false, this.onCircleShape); } protected addOnCenterTimeLine(pressedTimeline: Timeline) { - this.onLabelShape.timeline(pressedTimeline); + this.onCenterTimeLine(pressedTimeline, true); } protected drawOffCenterColor(mainColor: PowerButtonColor) { - this.offLabelShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + this.offCenterColor(mainColor, true); } protected onCenterAnimation(scale: number) { - powerButtonAnimation(this.onLabelShape).transform({scale, origin: {x: cx, y: cy}}); + this.buttonAnimation(scale, true); } protected drawShape() { @@ -1047,7 +1182,6 @@ class OutlinedVolumePowerButtonShape extends PowerButtonShape { this.onCircleShape = this.svgShape.circle(powerButtonShapeSize - 30).center(cx, cy) .addTo(this.onCenterGroup); this.drawOnCenter(); - this.addOnCenterToMask(this.onCircleShape); this.pressedShadow = new InnerShadowCircle(this.svgShape, powerButtonShapeSize - 30, cx, cy, 0, 0); this.backgroundShape.addClass('tb-small-shadow'); @@ -1102,37 +1236,24 @@ class OutlinedVolumePowerButtonShape extends PowerButtonShape { } class OutlinedIconPowerButtonShape extends OutlinedVolumePowerButtonShape { - private offPowerSymbolCircle: Path; - private offPowerSymbolLine: Path; - private onPowerSymbolCircle: Path; - private onPowerSymbolLine: Path; protected drawOffCenter(centerGroup: G) { - this.offPowerSymbolCircle = this.svgShape.path(powerCircleStroke).center(cx, cy).addTo(centerGroup); - this.offPowerSymbolLine = this.svgShape.path(powerLineStroke).center(cx, cy-12).addTo(centerGroup); + this.drawOffShape(centerGroup, false, '', true); } protected drawOnCenter() { - this.onPowerSymbolCircle = this.svgShape.path(powerCircleStroke).center(cx, cy); - this.onPowerSymbolLine = this.svgShape.path(powerLineStroke).center(cx, cy-12); - } - - protected addOnCenterToMask(onCircleShape: Circle) { - this.createMask(onCircleShape, [this.onPowerSymbolCircle, this.onPowerSymbolLine]); + this.drawOnShape(null, false, '', true, this.onCircleShape); } protected addOnCenterTimeLine(pressedTimeline: Timeline) { - this.onPowerSymbolCircle.timeline(pressedTimeline); - this.onPowerSymbolLine.timeline(pressedTimeline); + this.onCenterTimeLine(pressedTimeline, false); } protected drawOffCenterColor(mainColor: PowerButtonColor) { - this.offPowerSymbolCircle.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); - this.offPowerSymbolLine.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + this.offCenterColor(mainColor, false); } protected onCenterAnimation(scale: number) { - powerButtonAnimation(this.onPowerSymbolCircle).transform({scale, origin: {x: cx, y: cy}}); - powerButtonAnimation(this.onPowerSymbolLine).transform({scale, origin: {x: cx, y: cy}}); + this.buttonAnimation(scale, false); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/value-stepper-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/value-stepper-widget.component.html new file mode 100644 index 0000000000..6ce4387c1a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/value-stepper-widget.component.html @@ -0,0 +1,43 @@ + +
    +
    + +
    +
    +
    +
    +
    {{ valueText }}
    +
    +
    +
    +
    + +
    diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/value-stepper-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/value-stepper-widget.component.scss new file mode 100644 index 0000000000..0061d793a2 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/value-stepper-widget.component.scss @@ -0,0 +1,87 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.tb-value-stepper-panel { + width: 100%; + height: 100%; + position: relative; + display: flex; + flex-direction: column; + gap: 16px; + padding: 20px 24px 24px 24px; + > div:not(.tb-value-stepper-overlay) { + z-index: 1; + } + .tb-value-stepper-overlay { + position: absolute; + top: 12px; + left: 12px; + bottom: 12px; + right: 12px; + } + div.tb-widget-title { + padding: 0; + } + .tb-value-stepper-content { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + min-width: 0; + min-height: 0; + height: 100%; + .tb-value-stepper-value-box { + display: flex; + align-items: center; + justify-content: center; + flex: 1; + height: 32px; + padding: 0 12px; + border-radius: 4px; + &-disabled { + border-color: rgba(0, 0, 0, 0.38); + } + } + .tb-button-shape { + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + &-left { + margin-right: 12px; + } + &-right { + margin-left: 12px; + } + svg { + .tb-small-shadow { + filter: drop-shadow(0px 0px 4px rgba(0, 0, 0, 0.2)); + } + .tb-shadow { + filter: drop-shadow(0px 0px 10px rgba(0, 0, 0, 0.15)); + } + } + &.tb-button-pointer { + svg { + .tb-hover-circle { + cursor: pointer; + } + } + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/value-stepper-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/value-stepper-widget.component.ts new file mode 100644 index 0000000000..783e730326 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/value-stepper-widget.component.ts @@ -0,0 +1,391 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + AfterViewInit, + ChangeDetectorRef, + Component, + DestroyRef, + ElementRef, + inject, + NgZone, + OnDestroy, + OnInit, + Renderer2, + ViewChild, + ViewEncapsulation +} from '@angular/core'; +import { BasicActionWidgetComponent, ValueSetter } from '@home/components/widget/lib/action/action-widget.models'; +import { backgroundStyle, ComponentStyle, overlayStyle, textStyle } from '@shared/models/widget-settings.models'; +import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; +import { ImagePipe } from '@shared/pipe/image.pipe'; +import { DomSanitizer } from '@angular/platform-browser'; +import { ValueType } from '@shared/models/constants'; +import { + PowerButtonLayout, + PowerButtonShape, + powerButtonShapeSize, + PowerButtonWidgetSettings +} from '@home/components/widget/lib/rpc/power-button-widget.models'; +import { SVG, Svg } from '@svgdotjs/svg.js'; +import { MatIconRegistry } from '@angular/material/icon'; +import { formatValue, isDefinedAndNotNull, isNumeric } from '@core/utils'; +import { + valueStepperDefaultSettings, + ValueStepperWidgetSettings +} from '@home/components/widget/lib/rpc/value-stepper-widget.models'; +import { UtilsService } from '@core/services/utils.service'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; + +@Component({ + selector: 'tb-value-stepper-widget', + templateUrl: './value-stepper-widget.component.html', + styleUrls: ['../action/action-widget.scss', './value-stepper-widget.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class ValueStepperWidgetComponent extends + BasicActionWidgetComponent implements OnInit, AfterViewInit, OnDestroy { + + @ViewChild('leftButton', {static: true}) + leftButton: ElementRef; + + @ViewChild('rightButton', {static: true}) + rightButton: ElementRef; + + @ViewChild('stepperContent', {static: true}) + stepperContent: ElementRef; + + @ViewChild('valueBoxContainer', {static: true}) + valueBox: ElementRef; + + @ViewChild('value', {static: true}) + valueElement: ElementRef; + + settings: ValueStepperWidgetSettings; + + backgroundStyle$: Observable; + overlayStyle: ComponentStyle = {}; + padding: string; + + valueStyle: ComponentStyle = {}; + valueStyleColor = ''; + disabledColor = 'rgba(0, 0, 0, 0.38)'; + value: number = null; + + autoScale = false; + + showValueBox = true; + showLeftButton = true; + showRightButton = true; + + valueText = 'N/A'; + + disabledState$ = new BehaviorSubject(false); + + private prevValue: number = null; + private shapeResize$: ResizeObserver; + private drawSvgShapePending = false; + private svgShapeLeft: Svg; + private svgShapeRight: Svg; + private powerButtonSvgShapeLeft: PowerButtonShape; + private powerButtonSvgShapeRight: PowerButtonShape; + + private disabledState = false; + public leftDisabledState = false; + public rightDisabledState = false; + + private valueSetterLeft: ValueSetter; + private valueSetterRight: ValueSetter; + + private leftDisabledState$ = new BehaviorSubject(false); + private rightDisabledState$ = new BehaviorSubject(false); + + protected destroyRef = inject(DestroyRef); + + constructor(protected imagePipe: ImagePipe, + protected sanitizer: DomSanitizer, + private renderer: Renderer2, + private iconRegistry: MatIconRegistry, + private utils: UtilsService, + private elementRef: ElementRef, + protected cd: ChangeDetectorRef, + protected zone: NgZone) { + super(cd); + } + + ngOnInit(): void { + super.ngOnInit(); + this.settings = {...valueStepperDefaultSettings, ...this.ctx.settings}; + + this.autoScale = this.settings.appearance.autoScale; + + this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer); + this.overlayStyle = overlayStyle(this.settings.background.overlay); + this.padding = this.settings.background.overlay.enabled ? undefined : this.settings.padding; + + this.showValueBox = this.settings.appearance.showValueBox; + this.showLeftButton = this.settings.buttonAppearance.leftButton.showButton; + this.showRightButton = this.settings.buttonAppearance.rightButton.showButton; + this.valueStyle = textStyle(this.settings.appearance.valueFont); + this.valueStyleColor = this.settings.appearance.valueColor; + + if (this.showValueBox) { + const valueBoxCss = `.tb-value-stepper-value-box {\n`+ + `border: ${this.settings.appearance.showBorder ? + `${this.settings.appearance.borderWidth}px solid ${this.settings.appearance.borderColor}` : + 'none'};\n`+ + `background-color: ${this.settings.appearance.valueBoxBackground}` + + `}`; + this.utils.applyCssToElement(this.renderer, this.elementRef.nativeElement, 'tb-value-stepper-value-box', valueBoxCss); + } + + const getInitialStateSettings = + {...this.settings.initialState, actionLabel: this.ctx.translate.instant('widgets.slider.initial-value')}; + this.createValueGetter(getInitialStateSettings, ValueType.INTEGER, { + next: (value) => this.onValue(value) + }); + const disabledStateSettings = + {...this.settings.disabledState, actionLabel: this.ctx.translate.instant('widgets.rpc-state.disabled-state')}; + this.createValueGetter(disabledStateSettings, ValueType.BOOLEAN, { + next: (value) => this.disabledState$.next(value) + }); + + const leftButtonClick = {...this.settings.leftButtonClick, + actionLabel: this.ctx.translate.instant('widgets.slider.on-value-change')}; + this.valueSetterLeft = this.createValueSetter(leftButtonClick); + + const rightButtonClick = {...this.settings.rightButtonClick, + actionLabel: this.ctx.translate.instant('widgets.slider.on-value-change')}; + this.valueSetterRight = this.createValueSetter(rightButtonClick); + + combineLatest([ + this.loading$, + this.disabledState$.asObservable(), + this.leftDisabledState$.asObservable() + ]).pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe(value => { + const state = value.includes(true); + this.updateLeftDisabledState(state) + }); + + combineLatest([ + this.loading$, + this.disabledState$.asObservable(), + this.rightDisabledState$.asObservable() + ]).pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe(value => { + const state = value.includes(true); + this.updateRightDisabledState(state) + }); + } + + ngAfterViewInit(): void { + if (this.drawSvgShapePending) { + this.drawSvg(); + } + super.ngAfterViewInit(); + } + + ngOnDestroy() { + if (this.shapeResize$) { + this.shapeResize$.disconnect(); + } + super.ngOnDestroy(); + } + + public onInit() { + super.onInit(); + const borderRadius = this.ctx.$widgetElement.css('borderRadius'); + this.overlayStyle = {...this.overlayStyle, ...{borderRadius}}; + if (this.leftButton || this.rightButton) { + this.drawSvg(); + } else { + this.drawSvgShapePending = true; + } + this.cd.detectChanges(); + } + + private onValue(value: number): void { + this.value = value; + this.prevValue = value; + if ((this.value + this.settings.appearance.valueStep) > this.settings.appearance.maxValueRange) { + this.rightDisabledState$.next(true); + } else { + this.rightDisabledState$.next(false); + } + if ((this.value - this.settings.appearance.valueStep) < this.settings.appearance.minValueRange) { + this.leftDisabledState$.next(true); + } else { + this.leftDisabledState$.next(false); + } + this.updateValueText(); + this.cd.markForCheck(); + } + + private updateValueText() { + if (isDefinedAndNotNull(this.value) && isNumeric(this.value)) { + this.valueText = formatValue(this.value, this.settings.appearance.valueDecimals, this.settings.appearance.valueUnits, false); + } else { + this.valueText = 'N/A'; + } + } + + private onClick(rightButtonClick: boolean = false) { + this.updateValueText(); + if (!this.ctx.isEdit && !this.ctx.isPreview && !this.disabledState) { + const prevValue = this.prevValue; + const targetValue = rightButtonClick ? + (this.value + this.settings.appearance.valueStep) : + (this.value - this.settings.appearance.valueStep); + this.updateValue(rightButtonClick ? this.valueSetterRight : this.valueSetterLeft, targetValue, { + next: () => this.onValue(targetValue), + error: () => this.onValue(prevValue) + }); + } + } + + private drawSvg() { + let leftButtonSetting: PowerButtonWidgetSettings; + let rightButtonSetting: PowerButtonWidgetSettings; + if (this.showLeftButton) { + this.svgShapeLeft = SVG().addTo(this.leftButton.nativeElement).size(powerButtonShapeSize, powerButtonShapeSize); + this.renderer.setStyle(this.svgShapeLeft.node, 'overflow', 'visible'); + this.renderer.setStyle(this.svgShapeLeft.node, 'user-select', 'none'); + leftButtonSetting = { + layout: PowerButtonLayout[this.settings.appearance.type], + onButtonIcon: { + showIcon: true, + icon: this.settings.buttonAppearance.leftButton.icon, + iconSize: this.settings.buttonAppearance.leftButton.iconSize * 1.7, + iconSizeUnit: this.settings.buttonAppearance.leftButton.iconSizeUnit + }, + offButtonIcon: { + showIcon: true, + icon: this.settings.buttonAppearance.leftButton.icon, + iconSize: this.settings.buttonAppearance.leftButton.iconSize * 1.7, + iconSizeUnit: this.settings.buttonAppearance.leftButton.iconSizeUnit + }, + mainColorOn: this.settings.buttonAppearance.leftButton.mainColorOn, + backgroundColorOn: this.settings.buttonAppearance.leftButton.backgroundColorOn, + mainColorOff: this.settings.buttonAppearance.leftButton.mainColorOff, + backgroundColorOff: this.settings.buttonAppearance.leftButton.backgroundColorOff, + mainColorDisabled: this.settings.buttonAppearance.leftButton.mainColorDisabled, + backgroundColorDisabled: this.settings.buttonAppearance.leftButton.backgroundColorDisabled + }; + } + if (this.showRightButton) { + this.svgShapeRight = SVG().addTo(this.rightButton.nativeElement).size(powerButtonShapeSize, powerButtonShapeSize); + this.renderer.setStyle(this.svgShapeRight.node, 'overflow', 'visible'); + this.renderer.setStyle(this.svgShapeRight.node, 'user-select', 'none'); + + rightButtonSetting = { + layout: PowerButtonLayout[this.settings.appearance.type], + onButtonIcon: { + showIcon: true, + icon: this.settings.buttonAppearance.rightButton.icon, + iconSize: this.settings.buttonAppearance.rightButton.iconSize * 1.7, + iconSizeUnit: this.settings.buttonAppearance.rightButton.iconSizeUnit + }, + offButtonIcon: { + showIcon: true, + icon: this.settings.buttonAppearance.rightButton.icon, + iconSize: this.settings.buttonAppearance.rightButton.iconSize * 1.7, + iconSizeUnit: this.settings.buttonAppearance.rightButton.iconSizeUnit + }, + mainColorOn: this.settings.buttonAppearance.rightButton.mainColorOn, + backgroundColorOn: this.settings.buttonAppearance.rightButton.backgroundColorOn, + mainColorOff: this.settings.buttonAppearance.rightButton.mainColorOff, + backgroundColorOff: this.settings.buttonAppearance.rightButton.backgroundColorOff, + mainColorDisabled: this.settings.buttonAppearance.rightButton.mainColorDisabled, + backgroundColorDisabled: this.settings.buttonAppearance.rightButton.backgroundColorDisabled + }; + } + + this.zone.run(() => { + if (this.showLeftButton) { + this.powerButtonSvgShapeLeft = PowerButtonShape.fromSettings(this.ctx, this.svgShapeLeft, this.iconRegistry, + leftButtonSetting , true, this.leftDisabledState, () => this.onClick()); + } + if (this.showRightButton) { + this.powerButtonSvgShapeRight = PowerButtonShape.fromSettings(this.ctx, this.svgShapeRight, this.iconRegistry, + rightButtonSetting, true, this.rightDisabledState, () => this.onClick(true)); + } + }); + + this.shapeResize$ = new ResizeObserver(() => { + this.onResize(); + }); + if (this.autoScale) { + this.shapeResize$.observe(this.stepperContent.nativeElement); + } + if (this.showLeftButton) { + this.shapeResize$.observe(this.leftButton.nativeElement); + } + if (this.showRightButton) { + this.shapeResize$.observe(this.rightButton.nativeElement); + } + this.onResize(); + } + + private updateLeftDisabledState(disabled: boolean) { + this.leftDisabledState = disabled; + this.powerButtonSvgShapeLeft?.setDisabled(this.leftDisabledState); + this.cd.markForCheck(); + } + + + private updateRightDisabledState(disabled: boolean) { + this.rightDisabledState = disabled; + this.powerButtonSvgShapeRight?.setDisabled(this.rightDisabledState); + this.cd.markForCheck(); + } + + private onResize() { + const panelWidth = this.stepperContent.nativeElement.getBoundingClientRect().width; + const panelHeight = this.stepperContent.nativeElement.getBoundingClientRect().height; + + const minAspect = 0.2; + const avgContentHeight = 32; + const targetHeight = panelWidth * Math.min(panelHeight / panelWidth, minAspect); + const multiplier = targetHeight / avgContentHeight; + const size = avgContentHeight * multiplier; + + if (this.showValueBox) { + this.renderer.setStyle(this.valueBox?.nativeElement, 'height', `${size}px`); + this.renderer.setStyle(this.valueElement?.nativeElement, 'font-size', `${this.settings.appearance.valueFont.size * multiplier}px`); + } + if (this.showLeftButton) { + this.renderer.setStyle(this.leftButton?.nativeElement, 'width', `${size}px`); + this.renderer.setStyle(this.leftButton?.nativeElement, 'height', `${size}px`); + } + if (this.showRightButton) { + this.renderer.setStyle(this.rightButton?.nativeElement, 'width', `${size}px`); + this.renderer.setStyle(this.rightButton?.nativeElement, 'height', `${size}px`); + } + if (size) { + const scale = size / powerButtonShapeSize; + if (this.showLeftButton) { + this.renderer.setStyle(this.svgShapeLeft?.node, 'transform', `scale(${scale})`); + } + if (this.showRightButton) { + this.renderer.setStyle(this.svgShapeRight?.node, 'transform', `scale(${scale})`); + } + } + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/value-stepper-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/value-stepper-widget.models.ts new file mode 100644 index 0000000000..9ec5ff5d65 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/value-stepper-widget.models.ts @@ -0,0 +1,252 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + + +import { + DataToValueType, + GetValueAction, + GetValueSettings, + SetValueAction, + SetValueSettings, + ValueToDataType +} from '@shared/models/action-widget-settings.models'; +import { WidgetButtonCustomStyles } from '@shared/components/button/widget-button.models'; +import { BackgroundSettings, BackgroundType, cssUnit, Font } from '@shared/models/widget-settings.models'; +import { AttributeScope } from '@shared/models/telemetry/telemetry.models'; + +const defaultMainColor = '#305680'; + +export enum ValueStepperType { + simplified = 'simplified', + default = 'default', + default_volume = 'default_volume' +} + +export const valueStepperTypes = Object.keys(ValueStepperType) as ValueStepperType[]; + +export const valueStepperTypeTranslations = new Map( + [ + [ValueStepperType.simplified, 'widgets.value-stepper.simplified'], + [ValueStepperType.default, 'widgets.value-stepper.filled'], + [ValueStepperType.default_volume, 'widgets.value-stepper.volume'] + ] +); + +export const valueStepperTypeImages = new Map( + [ + [ValueStepperType.simplified, 'assets/widget/value-stepper/simplified.svg'], + [ValueStepperType.default, 'assets/widget/value-stepper/filled.svg'], + [ValueStepperType.default_volume, 'assets/widget/value-stepper/volume.svg'] + ] +); + +export interface ValueStepperWidgetSettings { + initialState: GetValueSettings; + leftButtonClick: SetValueSettings; + rightButtonClick: SetValueSettings; + disabledState: GetValueSettings; + + appearance: ValueStepperAppearance; + buttonAppearance: { + leftButton: ValueStepperButtonAppearance; + rightButton: ValueStepperButtonAppearance; + } + + background: BackgroundSettings; + padding: string; +} + +export interface ValueStepperAppearance { + type: ValueStepperType; + autoScale: boolean; + minValueRange: number; + maxValueRange: number; + valueStep: number; + showValueBox: boolean; + valueUnits: string; + valueDecimals: number; + valueFont: Font; + valueColor: string; + valueBoxBackground: string; + showBorder: boolean; + borderWidth: number; + borderColor: string; +} + +export interface ValueStepperButtonAppearance { + showButton: boolean; + icon: string; + iconSize: number; + iconSizeUnit: cssUnit; + mainColorOn: string; + backgroundColorOn: string; + mainColorOff: string; + backgroundColorOff: string; + mainColorDisabled: string; + backgroundColorDisabled: string; + customStyle: WidgetButtonCustomStyles; +} + +export const valueStepperDefaultAppearance: ValueStepperAppearance = { + type: ValueStepperType.simplified, + autoScale: true, + minValueRange: -100, + maxValueRange: 100, + valueStep: 0.5, + showValueBox: true, + valueUnits: '', + valueDecimals: 1, + valueFont: { + family: 'Roboto', + weight: '500', + style: 'normal', + size: 16, + sizeUnit: 'px', + lineHeight: '24px' + }, + valueColor: '#000', + valueBoxBackground: 'rgba(0, 0, 0, 0.12)', + showBorder: true, + borderWidth: 1, + borderColor: defaultMainColor +} + +export const valueStepperButtonDefaultAppearance: ValueStepperButtonAppearance = { + showButton: true, + icon: '', + iconSize: 24, + iconSizeUnit: 'px', + + mainColorOn: '#3F52DD', + backgroundColorOn: '#FFFFFF', + mainColorOff: '#A2A2A2', + backgroundColorOff: '#FFFFFF', + mainColorDisabled: 'rgba(0,0,0,0.12)', + backgroundColorDisabled: '#FFFFFF', + customStyle: { + enabled: null, + hovered: null, + pressed: null, + activated: null, + disabled: null + } +} + +export const valueStepperDefaultSettings: ValueStepperWidgetSettings = { + initialState: { + action: GetValueAction.EXECUTE_RPC, + defaultValue: 0, + executeRpc: { + method: 'getState', + requestTimeout: 5000, + requestPersistent: false, + persistentPollingInterval: 1000 + }, + getAttribute: { + key: 'state', + scope: null + }, + getTimeSeries: { + key: 'state' + }, + getAlarmStatus: { + severityList: null, + typeList: null + }, + dataToValue: { + type: DataToValueType.NONE, + compareToValue: true, + dataToValueFunction: '/* Should return integer value */\nreturn data;' + } + }, + disabledState: { + action: GetValueAction.DO_NOTHING, + defaultValue: false, + getAttribute: { + key: 'state', + scope: null + }, + getTimeSeries: { + key: 'state' + }, + getAlarmStatus: { + severityList: null, + typeList: null + }, + dataToValue: { + type: DataToValueType.NONE, + compareToValue: true, + dataToValueFunction: '/* Should return boolean value */\nreturn data;' + } + }, + leftButtonClick: { + action: SetValueAction.EXECUTE_RPC, + executeRpc: { + method: 'setState', + requestTimeout: 5000, + requestPersistent: false, + persistentPollingInterval: 1000 + }, + setAttribute: { + key: 'state', + scope: AttributeScope.SERVER_SCOPE + }, + putTimeSeries: { + key: 'state' + }, + valueToData: { + type: ValueToDataType.VALUE, + constantValue: 0, + valueToDataFunction: '/* Convert input integer value to RPC parameters or attribute/time-series value */\nreturn value;' + } + }, + rightButtonClick: { + action: SetValueAction.EXECUTE_RPC, + executeRpc: { + method: 'setState', + requestTimeout: 5000, + requestPersistent: false, + persistentPollingInterval: 1000 + }, + setAttribute: { + key: 'state', + scope: AttributeScope.SERVER_SCOPE + }, + putTimeSeries: { + key: 'state' + }, + valueToData: { + type: ValueToDataType.VALUE, + constantValue: 0, + valueToDataFunction: '/* Convert input integer value to RPC parameters or attribute/time-series value */\nreturn value;' + } + }, + appearance: valueStepperDefaultAppearance, + buttonAppearance: { + leftButton: {...valueStepperButtonDefaultAppearance, icon: 'arrow_back_ios_new'}, + rightButton: {...valueStepperButtonDefaultAppearance, icon: 'arrow_forward_ios'} + }, + background: { + type: BackgroundType.color, + color: '#fff', + overlay: { + enabled: false, + color: 'rgba(255,255,255,0.72)', + blur: 3 + } + }, + padding: '12px' +}; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings-panel.component.html index cb6cd5cbe0..9c9f45173d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings-panel.component.html @@ -131,7 +131,9 @@ class="tb-form-panel stroked" formGroupName="dataToValue">
    widgets.value-action.action-result-converter
    - + {{ 'widgets.value-action.converter-none' | translate }} {{ 'widgets.value-action.converter-function' | translate }} @@ -143,7 +145,7 @@ [globalVariables]="functionScopeVariables" [functionArgs]="['data']" functionTitle="{{ 'widgets.value-action.parse-value-function' | translate }}" - helpId="widget/lib/rpc/parse_value_fn"> + [helpId]="getParseValueFunctionHelpId()">
    {{ 'widgets.value-action.state-when-result-is' | translate:{state: stateLabel} }}
    diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings-panel.component.ts index a4339b7e12..9b8b86e626 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings-panel.component.ts @@ -99,6 +99,8 @@ export class GetValueActionSettingsPanelComponent extends PageComponent implemen getValueSettingsFormGroup: UntypedFormGroup; + entityType = EntityType; + alarmSeverities = Object.keys(AlarmSeverity) as AlarmSeverity[]; alarmSeverityTranslationMap = alarmSeverityTranslations; @@ -165,9 +167,17 @@ export class GetValueActionSettingsPanelComponent extends PageComponent implemen this.getValueSettingsApplied.emit(getValueSettings); } + getParseValueFunctionHelpId(): string { + const action: GetValueAction = this.getValueSettingsFormGroup.get('action').value; + if (action === GetValueAction.GET_DASHBOARD_STATE_OBJECT) { + return 'widget/config/parse_value_get_dashboard_state_object_fn'; + } + return 'widget/lib/rpc/parse_value_fn'; + } + private updateValidators() { const action: GetValueAction = this.getValueSettingsFormGroup.get('action').value; - const dataToValueType: DataToValueType = this.getValueSettingsFormGroup.get('dataToValue').get('type').value; + let dataToValueType: DataToValueType = this.getValueSettingsFormGroup.get('dataToValue').get('type').value; this.getValueSettingsFormGroup.get('defaultValue').disable({emitEvent: false}); this.getValueSettingsFormGroup.get('executeRpc').disable({emitEvent: false}); @@ -196,6 +206,10 @@ export class GetValueActionSettingsPanelComponent extends PageComponent implemen case GetValueAction.GET_ALARM_STATUS: this.getValueSettingsFormGroup.get('getAlarmStatus').enable({emitEvent: false}); break; + case GetValueAction.GET_DASHBOARD_STATE_OBJECT: + this.getValueSettingsFormGroup.get('dataToValue.type').setValue(DataToValueType.FUNCTION, {emitEvent: false}); + dataToValueType = DataToValueType.FUNCTION; + break } if (action === GetValueAction.DO_NOTHING || action === GetValueAction.GET_ALARM_STATUS) { this.getValueSettingsFormGroup.get('dataToValue').disable({emitEvent: false}); @@ -208,6 +222,4 @@ export class GetValueActionSettingsPanelComponent extends PageComponent implemen } } } - - protected readonly entityType = EntityType; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings.component.ts index 871923e5b7..5e2661df59 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings.component.ts @@ -191,6 +191,14 @@ export class GetValueActionSettingsComponent implements OnInit, ControlValueAcce this.displayValue = this.translate.instant('widgets.value-action.get-dashboard-state-text'); } break; + case GetValueAction.GET_DASHBOARD_STATE_OBJECT: + if (this.valueType === ValueType.BOOLEAN) { + const state = this.modelValue.dataToValue?.compareToValue; + this.displayValue = this.translate.instant('widgets.value-action.when-dashboard-state-object-function-is-text', {state}); + } else { + this.displayValue = this.translate.instant('widgets.value-action.get-dashboard-state-object-text'); + } + break; } this.cd.markForCheck(); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/value-stepper-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/value-stepper-widget-settings.component.html new file mode 100644 index 0000000000..ebc106794e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/value-stepper-widget-settings.component.html @@ -0,0 +1,266 @@ + + +
    +
    widgets.value-stepper.behavior
    +
    +
    widgets.value-stepper.initial-state
    + +
    +
    +
    widgets.value-stepper.left-button-click
    + +
    +
    +
    widgets.value-stepper.right-button-click
    + +
    +
    +
    widgets.button-state.disabled-state
    + +
    +
    +
    +
    widget-config.appearance
    + + + {{ valueStepperTypeTranslationMap.get(type) | translate }} + + +
    + + {{ 'widgets.value-stepper.auto-scale' | translate }} + +
    + +
    +
    {{ 'widgets.value-stepper.value-range' | translate }}
    +
    +
    widgets.value-stepper.min-range
    + + + +
    widgets.value-stepper.max-range
    + + + +
    +
    +
    +
    {{ 'widgets.value-stepper.value-increment-decrement-step' | translate }}
    + + + +
    +
    + + {{ 'widgets.value-stepper.value' | translate }} + +
    + + + +
    widget-config.decimals-suffix
    +
    + + + + +
    +
    + +
    +
    {{ 'widgets.value-stepper.value-box-background' | translate }}
    + + +
    +
    + + {{ 'widgets.value-stepper.border' | translate }} + +
    + + +
    px
    +
    + + +
    +
    +
    +
    +
    +
    widgets.value-stepper.button-appearance
    + + {{ 'widgets.value-stepper.left' | translate }} + {{ 'widgets.value-stepper.right' | translate }} + +
    +
    +
    + + {{ 'widgets.value-stepper.left-button' | translate }} + +
    +
    +
    {{ 'widgets.value-stepper.icon' | translate }}
    +
    + + + + + + +
    +
    +
    +
    {{ 'widgets.power-button.power-on-colors' | translate }}
    +
    +
    +
    widgets.power-button.main
    + + +
    + +
    +
    widgets.power-button.background
    + + +
    +
    +
    +
    +
    {{ 'widgets.power-button.disabled-colors' | translate }}
    +
    +
    +
    widgets.power-button.main
    + + +
    + +
    +
    widgets.power-button.background
    + + +
    +
    +
    +
    +
    +
    + + {{ 'widgets.value-stepper.right-button' | translate }} + +
    +
    +
    {{ 'widgets.value-stepper.icon' | translate }}
    +
    + + + + + + +
    +
    +
    +
    {{ 'widgets.power-button.power-on-colors' | translate }}
    +
    +
    +
    widgets.power-button.main
    + + +
    + +
    +
    widgets.power-button.background
    + + +
    +
    +
    +
    +
    {{ 'widgets.power-button.disabled-colors' | translate }}
    +
    +
    +
    widgets.power-button.main
    + + +
    + +
    +
    widgets.power-button.background
    + + +
    +
    +
    +
    +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/value-stepper-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/value-stepper-widget-settings.component.ts new file mode 100644 index 0000000000..119c099da0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/value-stepper-widget-settings.component.ts @@ -0,0 +1,191 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { TargetDevice, WidgetSettings, WidgetSettingsComponent, widgetType } from '@shared/models/widget.models'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { ValueType } from '@shared/models/constants'; +import { getTargetDeviceFromDatasources } from '@shared/models/widget-settings.models'; +import { + valueStepperDefaultSettings, + valueStepperTypeImages, + valueStepperTypes, + valueStepperTypeTranslations +} from '@home/components/widget/lib/rpc/value-stepper-widget.models'; +import { formatValue } from '@core/utils'; + +type ButtonAppearanceType = 'left' | 'right'; + +@Component({ + selector: 'tb-value-stepper-widget-settings', + templateUrl: './value-stepper-widget-settings.component.html', + styleUrls: ['../widget-settings.scss'] +}) +export class ValueStepperWidgetSettingsComponent extends WidgetSettingsComponent { + + get targetDevice(): TargetDevice { + const datasources = this.widgetConfig?.config?.datasources; + return getTargetDeviceFromDatasources(datasources); + } + + get widgetType(): widgetType { + return this.widgetConfig?.widgetType; + } + get borderRadius(): string { + return this.widgetConfig?.config?.borderRadius; + } + + valueType = ValueType; + + valueStepperWidgetSettingsForm: UntypedFormGroup; + + valueStepperTypeTranslationMap = valueStepperTypeTranslations; + valueStepperTypes = valueStepperTypes; + valueStepperTypeImageMap = valueStepperTypeImages; + + buttonAppearanceType: ButtonAppearanceType = 'left'; + + valuePreviewFn = this._valuePreviewFn.bind(this); + + constructor(protected store: Store, + private fb: UntypedFormBuilder) { + super(store); + } + + protected settingsForm(): UntypedFormGroup { + return this.valueStepperWidgetSettingsForm; + } + + protected defaultSettings(): WidgetSettings { + return {...valueStepperDefaultSettings}; + } + + protected onSettingsSet(settings: WidgetSettings) { + this.valueStepperWidgetSettingsForm = this.fb.group({ + initialState: [settings.initialState, []], + leftButtonClick: [settings.leftButtonClick, []], + rightButtonClick: [settings.rightButtonClick, []], + disabledState: [settings.disabledState, []], + + appearance: this.fb.group({ + type: [settings.appearance.type, []], + autoScale: [settings.appearance.autoScale, []], + minValueRange: [settings.appearance.minValueRange, []], + maxValueRange: [settings.appearance.maxValueRange, []], + valueStep: [settings.appearance.valueStep, [Validators.min(0)]], + showValueBox: [settings.appearance.showValueBox, []], + valueUnits: [settings.appearance.valueUnits, []], + valueDecimals: [settings.appearance.valueDecimals, []], + valueFont: [settings.appearance.valueFont, []], + valueColor: [settings.appearance.valueColor], + valueBoxBackground: [settings.appearance.valueBoxBackground, []], + showBorder: [settings.appearance.showBorder, []], + borderWidth: [settings.appearance.borderWidth, []], + borderColor: [settings.appearance.borderColor, []] + }), + + buttonAppearance: this.fb.group({ + leftButton: this.fb.group({ + showButton: [settings.buttonAppearance.leftButton.showButton], + icon: [settings.buttonAppearance.leftButton.icon], + iconSize: [settings.buttonAppearance.leftButton.iconSize], + iconSizeUnit: [settings.buttonAppearance.leftButton.iconSizeUnit], + mainColorOn: [settings.buttonAppearance.leftButton.mainColorOn, []], + backgroundColorOn: [settings.buttonAppearance.leftButton.backgroundColorOn, []], + mainColorDisabled: [settings.buttonAppearance.leftButton.mainColorDisabled, []], + backgroundColorDisabled: [settings.buttonAppearance.leftButton.backgroundColorDisabled, []] + }), + rightButton: this.fb.group({ + showButton: [settings.buttonAppearance.rightButton.showButton], + icon: [settings.buttonAppearance.rightButton.icon], + iconSize: [settings.buttonAppearance.rightButton.iconSize], + iconSizeUnit: [settings.buttonAppearance.rightButton.iconSizeUnit], + mainColorOn: [settings.buttonAppearance.rightButton.mainColorOn, []], + backgroundColorOn: [settings.buttonAppearance.rightButton.backgroundColorOn, []], + mainColorDisabled: [settings.buttonAppearance.rightButton.mainColorDisabled, []], + backgroundColorDisabled: [settings.buttonAppearance.rightButton.backgroundColorDisabled, []] + }) + }) + }); + } + + + protected validatorTriggers(): string[] { + return ['appearance.showValueBox', 'appearance.showBorder', + 'buttonAppearance.leftButton.showButton', 'buttonAppearance.rightButton.showButton']; + } + + protected updateValidators(_emitEvent: boolean): void { + const showValueBox: boolean = this.valueStepperWidgetSettingsForm.get('appearance').get('showValueBox').value; + const showBorder: boolean = this.valueStepperWidgetSettingsForm.get('appearance').get('showBorder').value; + const showLeftButton: boolean = this.valueStepperWidgetSettingsForm.get('buttonAppearance').get('leftButton').get('showButton').value; + const showRightButton: boolean = this.valueStepperWidgetSettingsForm.get('buttonAppearance').get('rightButton').get('showButton').value; + if (showValueBox) { + this.valueStepperWidgetSettingsForm.get('appearance').get('valueUnits').enable(); + this.valueStepperWidgetSettingsForm.get('appearance').get('valueDecimals').enable(); + this.valueStepperWidgetSettingsForm.get('appearance').get('valueFont').enable(); + this.valueStepperWidgetSettingsForm.get('appearance').get('valueColor').enable(); + this.valueStepperWidgetSettingsForm.get('appearance').get('valueBoxBackground').enable(); + this.valueStepperWidgetSettingsForm.get('appearance').get('showBorder').enable({emitEvent: false}); + if (showBorder) { + this.valueStepperWidgetSettingsForm.get('appearance').get('borderWidth').enable(); + this.valueStepperWidgetSettingsForm.get('appearance').get('borderColor').enable(); + } else { + this.valueStepperWidgetSettingsForm.get('appearance').get('borderWidth').disable(); + this.valueStepperWidgetSettingsForm.get('appearance').get('borderColor').disable(); + } + } else { + this.valueStepperWidgetSettingsForm.get('appearance').get('valueUnits').disable(); + this.valueStepperWidgetSettingsForm.get('appearance').get('valueDecimals').disable(); + this.valueStepperWidgetSettingsForm.get('appearance').get('valueFont').disable(); + this.valueStepperWidgetSettingsForm.get('appearance').get('valueColor').disable(); + this.valueStepperWidgetSettingsForm.get('appearance').get('valueBoxBackground').disable(); + this.valueStepperWidgetSettingsForm.get('appearance').get('showBorder').disable({emitEvent: false}); + this.valueStepperWidgetSettingsForm.get('appearance').get('borderWidth').disable(); + this.valueStepperWidgetSettingsForm.get('appearance').get('borderColor').disable(); + } + this.buttonValidators(showLeftButton, 'leftButton'); + this.buttonValidators(showRightButton, 'rightButton'); + } + + private buttonValidators(showButtonValue: boolean, button: string) { + if (showButtonValue) { + this.valueStepperWidgetSettingsForm.get('buttonAppearance').get(button).get('icon').enable() + this.valueStepperWidgetSettingsForm.get('buttonAppearance').get(button).get('iconSize').enable() + this.valueStepperWidgetSettingsForm.get('buttonAppearance').get(button).get('iconSizeUnit').enable() + this.valueStepperWidgetSettingsForm.get('buttonAppearance').get(button).get('mainColorOn').enable() + this.valueStepperWidgetSettingsForm.get('buttonAppearance').get(button).get('backgroundColorOn').enable() + this.valueStepperWidgetSettingsForm.get('buttonAppearance').get(button).get('mainColorDisabled').enable() + this.valueStepperWidgetSettingsForm.get('buttonAppearance').get(button).get('backgroundColorDisabled').enable() + } else { + this.valueStepperWidgetSettingsForm.get('buttonAppearance').get(button).get('icon').disable() + this.valueStepperWidgetSettingsForm.get('buttonAppearance').get(button).get('iconSize').disable() + this.valueStepperWidgetSettingsForm.get('buttonAppearance').get(button).get('iconSizeUnit').disable() + this.valueStepperWidgetSettingsForm.get('buttonAppearance').get(button).get('mainColorOn').disable() + this.valueStepperWidgetSettingsForm.get('buttonAppearance').get(button).get('backgroundColorOn').disable() + this.valueStepperWidgetSettingsForm.get('buttonAppearance').get(button).get('mainColorDisabled').disable() + this.valueStepperWidgetSettingsForm.get('buttonAppearance').get(button).get('backgroundColorDisabled').disable() + } + } + + private _valuePreviewFn(): string { + const units: string = this.valueStepperWidgetSettingsForm.get('appearance').get('valueUnits').value; + const decimals: number = this.valueStepperWidgetSettingsForm.get('appearance').get('valueDecimals').value; + return formatValue(48, decimals, units, false); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/liquid-level-card-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/liquid-level-card-widget-settings.component.html index a1aae64747..8d787116a8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/liquid-level-card-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/liquid-level-card-widget-settings.component.html @@ -18,7 +18,7 @@
    -
    +
    widgets.liquid-level-card.shape
    diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/liquid-level-card-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/liquid-level-card-widget-settings.component.ts index 70a163ac1a..11454392b4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/liquid-level-card-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/liquid-level-card-widget-settings.component.ts @@ -56,7 +56,7 @@ import { EntityService } from '@core/http/entity.service'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @Component({ - selector: 'liquid-level-card-widget-settings', + selector: 'tb-liquid-level-card-widget-settings', templateUrl: './liquid-level-card-widget-settings.component.html', styleUrls: [] }) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.html index 93c53c27ec..37a4dee4d5 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.html @@ -21,108 +21,113 @@
    widgets.input-widgets.general-settings -
    - - widgets.input-widgets.datakey-type - - - {{ 'widgets.input-widgets.datakey-type-server' | translate }} - - - {{ 'widgets.input-widgets.datakey-type-shared' | translate }} - - - {{ 'widgets.input-widgets.datakey-type-timeseries' | translate }} - - - - - widgets.input-widgets.datakey-value-type - - - {{ 'widgets.input-widgets.datakey-value-type-string' | translate }} - - - {{ 'widgets.input-widgets.datakey-value-type-double' | translate }} - - - {{ 'widgets.input-widgets.datakey-value-type-integer' | translate }} - - - {{ 'widgets.input-widgets.datakey-value-type-boolean-checkbox' | translate }} - - - {{ 'widgets.input-widgets.datakey-value-type-boolean-switch' | translate }} - - - {{ 'widgets.input-widgets.datakey-value-type-date-time' | translate }} - - - {{ 'widgets.input-widgets.datakey-value-type-date' | translate }} - - - {{ 'widgets.input-widgets.datakey-value-type-time' | translate }} - - - {{ 'widgets.input-widgets.datakey-value-type-select' | translate }} - - - {{ 'widgets.input-widgets.datakey-value-type-json' | translate }} - - - {{ 'widgets.input-widgets.datakey-value-type-color' | translate }} - - - -
    - - {{ 'widgets.input-widgets.value-is-required' | translate }} - -
    - - widgets.input-widgets.ability-to-edit-attribute - - - {{ 'widgets.input-widgets.ability-to-edit-attribute-editable' | translate }} - - - {{ 'widgets.input-widgets.ability-to-edit-attribute-disabled' | translate }} - - - {{ 'widgets.input-widgets.ability-to-edit-attribute-readonly' | translate }} - - - - - widgets.input-widgets.disable-on-datakey-name - - -
    -
    - - widgets.input-widgets.field-appearance - - - {{ 'widgets.input-widgets.appearance-fill' | translate }} - - - {{ 'widgets.input-widgets.appearance-outline' | translate }} - - - - - widgets.input-widgets.subscript-sizing - - - {{ 'widgets.input-widgets.subscript-sizing-fixed' | translate }} - - - {{ 'widgets.input-widgets.subscript-sizing-dynamic' | translate }} - - - -
    +
    +
    + + widgets.input-widgets.datakey-type + + + {{ 'widgets.input-widgets.datakey-type-server' | translate }} + + + {{ 'widgets.input-widgets.datakey-type-shared' | translate }} + + + {{ 'widgets.input-widgets.datakey-type-timeseries' | translate }} + + + + + widgets.input-widgets.datakey-value-type + + + {{ 'widgets.input-widgets.datakey-value-type-string' | translate }} + + + {{ 'widgets.input-widgets.datakey-value-type-double' | translate }} + + + {{ 'widgets.input-widgets.datakey-value-type-integer' | translate }} + + + {{ 'widgets.input-widgets.datakey-value-type-boolean-checkbox' | translate }} + + + {{ 'widgets.input-widgets.datakey-value-type-boolean-switch' | translate }} + + + {{ 'widgets.input-widgets.datakey-value-type-date-time' | translate }} + + + {{ 'widgets.input-widgets.datakey-value-type-date' | translate }} + + + {{ 'widgets.input-widgets.datakey-value-type-time' | translate }} + + + {{ 'widgets.input-widgets.datakey-value-type-select' | translate }} + + + {{ 'widgets.input-widgets.datakey-value-type-radio' | translate }} + + + {{ 'widgets.input-widgets.datakey-value-type-json' | translate }} + + + {{ 'widgets.input-widgets.datakey-value-type-color' | translate }} + + + +
    + + {{ 'widgets.input-widgets.value-is-required' | translate }} + +
    + + widgets.input-widgets.ability-to-edit-attribute + + + {{ 'widgets.input-widgets.ability-to-edit-attribute-editable' | translate }} + + + {{ 'widgets.input-widgets.ability-to-edit-attribute-disabled' | translate }} + + + {{ 'widgets.input-widgets.ability-to-edit-attribute-readonly' | translate }} + + + + + widgets.input-widgets.disable-on-datakey-name + + +
    +
    + + widgets.input-widgets.field-appearance + + + {{ 'widgets.input-widgets.appearance-fill' | translate }} + + + {{ 'widgets.input-widgets.appearance-outline' | translate }} + + + + + widgets.input-widgets.subscript-sizing + + + {{ 'widgets.input-widgets.subscript-sizing-fixed' | translate }} + + + {{ 'widgets.input-widgets.subscript-sizing-dynamic' | translate }} + + + +
    +
    @@ -140,8 +145,10 @@
    - widgets.input-widgets.select-options + (updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value !== 'select' && updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value !== 'radio')" class="fields-group"> + + {{ (updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value === 'select' ? 'widgets.input-widgets.select-options' : 'widgets.input-widgets.radio-options') | translate }} +
    @@ -155,18 +162,51 @@
    - widgets.input-widgets.no-select-options + + {{ (updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value === 'select' ? 'widgets.input-widgets.no-select-options' : 'widgets.input-widgets.no-radio-options') | translate }} +
    +
    +
    widgets.input-widgets.radio-button-settings
    +
    +
    {{ 'widgets.input-widgets.color' | translate }}
    + + +
    +
    +
    {{ 'widgets.input-widgets.columns' | translate }}
    + + + +
    +
    +
    {{ 'widgets.input-widgets.radio-label-position' | translate }}
    + + + + {{ 'widgets.input-widgets.radio-label-position-after' | translate }} + + + {{ 'widgets.input-widgets.radio-label-position-before' | translate }} + + + +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.ts index 24e0ac7072..378c2fdeab 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.ts @@ -60,6 +60,9 @@ export class UpdateMultipleAttributesKeySettingsComponent extends WidgetSettings slideToggleLabelPosition: 'after', selectOptions: [], + radioColor: null, + radioColumns: 1, + radioLabelPosition: 'after', step: 1, minValue: null, maxValue: null, @@ -104,10 +107,16 @@ export class UpdateMultipleAttributesKeySettingsComponent extends WidgetSettings slideToggleLabelPosition: [settings.slideToggleLabelPosition, []], - // Select options + // Select/Radio options selectOptions: this.prepareSelectOptionsFormArray(settings.selectOptions), + // Radio settings + + radioColor: [settings.radioColor, []], + radioColumns: [settings.radioColumns, []], + radioLabelPosition: [settings.radioLabelPosition, []], + // Numeric field settings step: [settings.step, [Validators.min(0)]], @@ -183,6 +192,11 @@ export class UpdateMultipleAttributesKeySettingsComponent extends WidgetSettings this.updateMultipleAttributesKeySettingsForm.get('slideToggleLabelPosition').enable({emitEvent: false}); } else if (dataKeyValueType === 'select') { this.updateMultipleAttributesKeySettingsForm.get('selectOptions').enable({emitEvent: false}); + } else if (dataKeyValueType === 'radio') { + this.updateMultipleAttributesKeySettingsForm.get('selectOptions').enable({emitEvent: false}); + this.updateMultipleAttributesKeySettingsForm.get('radioColor').enable({emitEvent: false}); + this.updateMultipleAttributesKeySettingsForm.get('radioColumns').enable({emitEvent: false}); + this.updateMultipleAttributesKeySettingsForm.get('radioLabelPosition').enable({emitEvent: false}); } else if (dataKeyValueType === 'integer' || dataKeyValueType === 'double') { this.updateMultipleAttributesKeySettingsForm.get('step').enable({emitEvent: false}); this.updateMultipleAttributesKeySettingsForm.get('minValue').enable({emitEvent: false}); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/map/trip-animation-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/map/trip-animation-widget-settings.component.ts index ab4ab92e17..88316d185c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/map/trip-animation-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/map/trip-animation-widget-settings.component.ts @@ -21,20 +21,20 @@ import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { CircleSettings, - CommonMapSettings, defaultCircleSettings, - defaultCommonMapSettings, defaultMapProviderSettings, - defaultMarkersSettings, defaultPolygonSettings, + defaultTripAnimationCommonSettings, + defaultTripAnimationMarkersSettings, defaultTripAnimationPathSettings, defaultTripAnimationPointSettings, defaultTripAnimationSettings, MapProviderSettings, - MarkersSettings, PointsSettings, PolygonSettings, - PolylineSettings + PolylineSettings, + TripAnimationCommonSettings, + TripAnimationMarkerSettings } from 'src/app/modules/home/components/widget/lib/maps/map-models'; import { extractType } from '@core/utils'; @@ -76,8 +76,8 @@ export class TripAnimationWidgetSettingsComponent extends WidgetSettingsComponen protected prepareInputSettings(settings: WidgetSettings): WidgetSettings { const mapProviderSettings = extractType(settings, Object.keys(defaultMapProviderSettings) as (keyof MapProviderSettings)[]); - const commonMapSettings = extractType(settings, Object.keys(defaultCommonMapSettings) as (keyof CommonMapSettings)[]); - const markersSettings = extractType(settings, Object.keys(defaultMarkersSettings) as (keyof MarkersSettings)[]); + const commonMapSettings = extractType(settings, Object.keys(defaultTripAnimationCommonSettings) as (keyof TripAnimationCommonSettings)[]); + const markersSettings = extractType(settings, Object.keys(defaultTripAnimationMarkersSettings) as (keyof TripAnimationMarkerSettings)[]); const pathSettings = extractType(settings, Object.keys(defaultTripAnimationPathSettings) as (keyof PolylineSettings)[]); const pointSettings = extractType(settings, Object.keys(defaultTripAnimationPointSettings) as (keyof PointsSettings)[]); const polygonSettings = extractType(settings, Object.keys(defaultPolygonSettings) as (keyof PolygonSettings)[]); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts index 4b5f3a1ad0..e50fcb30f0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts @@ -14,14 +14,14 @@ /// limitations under the License. /// -import { NgModule, Type } from '@angular/core'; -import { - QrCodeWidgetSettingsComponent -} from '@home/components/widget/lib/settings/cards/qrcode-widget-settings.component'; +import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { SharedModule } from '@shared/shared.module'; +import { WidgetService } from '@core/http/widget.service'; import { SharedHomeComponentsModule } from '@home/components/shared-home-components.module'; -import { IWidgetSettingsComponent } from '@shared/models/widget.models'; +import { + QrCodeWidgetSettingsComponent +} from '@home/components/widget/lib/settings/cards/qrcode-widget-settings.component'; import { TimeseriesTableWidgetSettingsComponent } from '@home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component'; @@ -366,11 +366,14 @@ import { UnreadNotificationWidgetSettingsComponent } from '@home/components/widget/lib/settings/cards/unread-notification-widget-settings.component'; import { -ScadaSymbolWidgetSettingsComponent + ScadaSymbolWidgetSettingsComponent } from '@home/components/widget/lib/settings/scada/scada-symbol-widget-settings.component'; import { SegmentedButtonWidgetSettingsComponent } from '@home/components/widget/lib/settings/button/segmented-button-widget-settings.component'; +import { + ValueStepperWidgetSettingsComponent +} from '@home/components/widget/lib/settings/control/value-stepper-widget-settings.component'; @NgModule({ declarations: [ @@ -487,6 +490,7 @@ import { SingleSwitchWidgetSettingsComponent, ActionButtonWidgetSettingsComponent, SegmentedButtonWidgetSettingsComponent, + ValueStepperWidgetSettingsComponent, CommandButtonWidgetSettingsComponent, PowerButtonWidgetSettingsComponent, SliderWidgetSettingsComponent, @@ -624,6 +628,7 @@ import { SingleSwitchWidgetSettingsComponent, ActionButtonWidgetSettingsComponent, SegmentedButtonWidgetSettingsComponent, + ValueStepperWidgetSettingsComponent, CommandButtonWidgetSettingsComponent, PowerButtonWidgetSettingsComponent, SliderWidgetSettingsComponent, @@ -643,105 +648,7 @@ import { ] }) export class WidgetSettingsModule { + constructor(private widgetService: WidgetService) { + this.widgetService.registerWidgetSettingsComponents(this.constructor) + } } - -export const widgetSettingsComponentsMap: {[key: string]: Type} = { - 'tb-qrcode-widget-settings': QrCodeWidgetSettingsComponent, - 'tb-mobile-app-qr-code-widget-settings': MobileAppQrCodeWidgetSettingsComponent, - 'tb-timeseries-table-widget-settings': TimeseriesTableWidgetSettingsComponent, - 'tb-timeseries-table-key-settings': TimeseriesTableKeySettingsComponent, - 'tb-timeseries-table-latest-key-settings': TimeseriesTableLatestKeySettingsComponent, - 'tb-markdown-widget-settings': MarkdownWidgetSettingsComponent, - 'tb-label-widget-settings': LabelWidgetSettingsComponent, - 'tb-simple-card-widget-settings': SimpleCardWidgetSettingsComponent, - 'tb-dashboard-state-widget-settings': DashboardStateWidgetSettingsComponent, - 'tb-entities-hierarchy-widget-settings': EntitiesHierarchyWidgetSettingsComponent, - 'tb-html-card-widget-settings': HtmlCardWidgetSettingsComponent, - 'tb-entities-table-widget-settings': EntitiesTableWidgetSettingsComponent, - 'tb-entities-table-key-settings': EntitiesTableKeySettingsComponent, - 'tb-alarms-table-widget-settings': AlarmsTableWidgetSettingsComponent, - 'tb-alarms-table-key-settings': AlarmsTableKeySettingsComponent, - 'tb-analogue-radial-gauge-widget-settings': AnalogueRadialGaugeWidgetSettingsComponent, - 'tb-analogue-linear-gauge-widget-settings': AnalogueLinearGaugeWidgetSettingsComponent, - 'tb-analogue-compass-widget-settings': AnalogueCompassWidgetSettingsComponent, - 'tb-digital-gauge-widget-settings': DigitalGaugeWidgetSettingsComponent, - 'tb-flot-line-widget-settings': FlotLineWidgetSettingsComponent, - 'tb-flot-bar-widget-settings': FlotBarWidgetSettingsComponent, - 'tb-flot-line-key-settings': FlotLineKeySettingsComponent, - 'tb-flot-bar-key-settings': FlotBarKeySettingsComponent, - 'tb-flot-latest-key-settings': FlotLatestKeySettingsComponent, - 'tb-flot-pie-widget-settings': FlotPieWidgetSettingsComponent, - 'tb-flot-pie-key-settings': FlotPieKeySettingsComponent, - 'tb-chart-widget-settings': ChartWidgetSettingsComponent, - 'tb-doughnut-chart-widget-settings': DoughnutChartWidgetSettingsComponent, - 'tb-round-switch-widget-settings': RoundSwitchWidgetSettingsComponent, - 'tb-switch-control-widget-settings': SwitchControlWidgetSettingsComponent, - 'tb-slide-toggle-widget-settings': SlideToggleWidgetSettingsComponent, - 'tb-persistent-table-widget-settings': PersistentTableWidgetSettingsComponent, - 'tb-update-device-attribute-widget-settings': UpdateDeviceAttributeWidgetSettingsComponent, - 'tb-send-rpc-widget-settings': SendRpcWidgetSettingsComponent, - 'tb-led-indicator-widget-settings': LedIndicatorWidgetSettingsComponent, - 'tb-knob-control-widget-settings': KnobControlWidgetSettingsComponent, - 'tb-rpc-terminal-widget-settings': RpcTerminalWidgetSettingsComponent, - 'tb-rpc-shell-widget-settings': RpcShellWidgetSettingsComponent, - 'tb-date-range-navigator-widget-settings': DateRangeNavigatorWidgetSettingsComponent, - 'tb-edge-quick-overview-widget-settings': EdgeQuickOverviewWidgetSettingsComponent, - 'tb-gateway-config-widget-settings': GatewayConfigWidgetSettingsComponent, - 'tb-gateway-config-single-device-widget-settings': GatewayConfigSingleDeviceWidgetSettingsComponent, - 'tb-gateway-events-widget-settings': GatewayEventsWidgetSettingsComponent, - 'tb-gpio-control-widget-settings': GpioControlWidgetSettingsComponent, - 'tb-gpio-panel-widget-settings': GpioPanelWidgetSettingsComponent, - 'tb-navigation-card-widget-settings': NavigationCardWidgetSettingsComponent, - 'tb-navigation-cards-widget-settings': NavigationCardsWidgetSettingsComponent, - 'tb-device-claiming-widget-settings': DeviceClaimingWidgetSettingsComponent, - 'tb-update-integer-attribute-widget-settings': UpdateIntegerAttributeWidgetSettingsComponent, - 'tb-update-double-attribute-widget-settings': UpdateDoubleAttributeWidgetSettingsComponent, - 'tb-update-string-attribute-widget-settings': UpdateStringAttributeWidgetSettingsComponent, - 'tb-update-boolean-attribute-widget-settings': UpdateBooleanAttributeWidgetSettingsComponent, - 'tb-update-image-attribute-widget-settings': UpdateImageAttributeWidgetSettingsComponent, - 'tb-update-date-attribute-widget-settings': UpdateDateAttributeWidgetSettingsComponent, - 'tb-update-location-attribute-widget-settings': UpdateLocationAttributeWidgetSettingsComponent, - 'tb-update-json-attribute-widget-settings': UpdateJsonAttributeWidgetSettingsComponent, - 'tb-photo-camera-input-widget-settings': PhotoCameraInputWidgetSettingsComponent, - 'tb-update-multiple-attributes-widget-settings': UpdateMultipleAttributesWidgetSettingsComponent, - 'tb-update-multiple-attributes-key-settings': UpdateMultipleAttributesKeySettingsComponent, - 'tb-map-widget-settings': MapWidgetSettingsComponent, - 'tb-route-map-widget-settings': RouteMapWidgetSettingsComponent, - 'tb-trip-animation-widget-settings': TripAnimationWidgetSettingsComponent, - 'tb-gateway-logs-settings': GatewayLogsSettingsComponent, - 'tb-gateway-service-rpc-settings':GatewayServiceRPCSettingsComponent, - 'tb-doc-links-widget-settings': DocLinksWidgetSettingsComponent, - 'tb-quick-links-widget-settings': QuickLinksWidgetSettingsComponent, - 'tb-value-card-widget-settings': ValueCardWidgetSettingsComponent, - 'tb-aggregated-value-card-key-settings': AggregatedValueCardKeySettingsComponent, - 'tb-aggregated-value-card-widget-settings': AggregatedValueCardWidgetSettingsComponent, - 'tb-alarm-count-widget-settings': AlarmCountWidgetSettingsComponent, - 'tb-entity-count-widget-settings': EntityCountWidgetSettingsComponent, - 'tb-battery-level-widget-settings': BatteryLevelWidgetSettingsComponent, - 'tb-wind-speed-direction-widget-settings': WindSpeedDirectionWidgetSettingsComponent, - 'tb-signal-strength-widget-settings': SignalStrengthWidgetSettingsComponent, - 'tb-value-chart-card-widget-settings': ValueChartCardWidgetSettingsComponent, - 'tb-progress-bar-widget-settings': ProgressBarWidgetSettingsComponent, - 'tb-liquid-level-card-widget-settings': LiquidLevelCardWidgetSettingsComponent, - 'tb-doughnut-widget-settings': DoughnutWidgetSettingsComponent, - 'tb-range-chart-widget-settings': RangeChartWidgetSettingsComponent, - 'tb-bar-chart-with-labels-widget-settings': BarChartWithLabelsWidgetSettingsComponent, - 'tb-single-switch-widget-settings': SingleSwitchWidgetSettingsComponent, - 'tb-action-button-widget-settings': ActionButtonWidgetSettingsComponent, - 'tb-segmented-button-widget-settings': SegmentedButtonWidgetSettingsComponent, - 'tb-command-button-widget-settings': CommandButtonWidgetSettingsComponent, - 'tb-power-button-widget-settings': PowerButtonWidgetSettingsComponent, - 'tb-slider-widget-settings': SliderWidgetSettingsComponent, - 'tb-toggle-button-widget-settings': ToggleButtonWidgetSettingsComponent, - 'tb-time-series-chart-key-settings': TimeSeriesChartKeySettingsComponent, - 'tb-time-series-chart-widget-settings': TimeSeriesChartWidgetSettingsComponent, - 'tb-status-widget-settings': StatusWidgetSettingsComponent, - 'tb-pie-chart-widget-settings': PieChartWidgetSettingsComponent, - 'tb-bar-chart-widget-settings': BarChartWidgetSettingsComponent, - 'tb-polar-area-chart-widget-settings': PolarAreaChartWidgetSettingsComponent, - 'tb-radar-chart-widget-settings': RadarChartWidgetSettingsComponent, - 'tb-label-card-widget-settings': LabelCardWidgetSettingsComponent, - 'tb-label-value-card-widget-settings': LabelValueCardWidgetSettingsComponent, - 'tb-unread-notification-widget-settings': UnreadNotificationWidgetSettingsComponent, - 'tb-scada-symbol-widget-settings': ScadaSymbolWidgetSettingsComponent -}; diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts b/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts index d9b18d896a..8c94dfcdde 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts @@ -36,7 +36,8 @@ import { ResourcesService } from '@core/services/resources.service'; import { - IWidgetSettingsComponent, migrateWidgetTypeToDynamicForms, + IWidgetSettingsComponent, + migrateWidgetTypeToDynamicForms, Widget, widgetActionSources, WidgetControllerDescriptor, @@ -58,8 +59,6 @@ import tinycolor from 'tinycolor2'; import moment from 'moment'; import { IModulesMap } from '@modules/common/modules-map.models'; import { HOME_COMPONENTS_MODULE_TOKEN } from '@home/components/tokens'; -import { widgetSettingsComponentsMap } from '@home/components/widget/lib/settings/widget-settings.module'; -import { basicWidgetConfigComponentsMap } from '@home/components/widget/config/basic/basic-widget-config.module'; import { IBasicWidgetConfigComponent } from '@home/components/widget/config/widget-config.component.models'; import { compileTbFunction, TbFunction } from '@shared/models/js-function.models'; import { HttpClient } from '@angular/common/http'; @@ -437,17 +436,17 @@ export class WidgetComponentService { basicDirectives.push(widgetInfo.basicModeDirective); } - this.expandSettingComponentMap(widgetSettingsComponentsMap, directives, modulesWithComponents); - this.expandSettingComponentMap(basicWidgetConfigComponentsMap, basicDirectives, modulesWithComponents); + this.expandSettingComponentMap(this.widgetService.putWidgetSettingsComponentToMap.bind(this.widgetService), directives, modulesWithComponents); + this.expandSettingComponentMap(this.widgetService.putBasicWidgetSettingsComponentToMap.bind(this.widgetService), basicDirectives, modulesWithComponents); } - private expandSettingComponentMap(settingsComponentsMap: {[key: string]: Type}, + private expandSettingComponentMap(putComponentToMap: (selector: string, comp: Type) => void, directives: string[], modulesWithComponents: ModulesWithComponents): void { if (directives.length) { directives.forEach(selector => { const compType = componentTypeBySelector(modulesWithComponents, selector); if (compType) { - settingsComponentsMap[selector] = compType; + putComponentToMap(selector, compType); } }); } diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts index 21f2d89dc6..fe752bb8b5 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts @@ -88,6 +88,7 @@ import { import { EllipsisChipListDirective } from '@shared/directives/ellipsis-chip-list.directive'; import { ScadaSymbolWidgetComponent } from '@home/components/widget/lib/scada/scada-symbol-widget.component'; import { TwoSegmentButtonWidgetComponent } from '@home/components/widget/lib/button/two-segment-button-widget.component'; +import { ValueStepperWidgetComponent } from '@home/components/widget/lib/rpc/value-stepper-widget.component'; @NgModule({ declarations: [ @@ -128,6 +129,7 @@ import { TwoSegmentButtonWidgetComponent } from '@home/components/widget/lib/but TwoSegmentButtonWidgetComponent, CommandButtonWidgetComponent, PowerButtonWidgetComponent, + ValueStepperWidgetComponent, SliderWidgetComponent, ToggleButtonWidgetComponent, TimeSeriesChartWidgetComponent, @@ -190,6 +192,7 @@ import { TwoSegmentButtonWidgetComponent } from '@home/components/widget/lib/but TwoSegmentButtonWidgetComponent, CommandButtonWidgetComponent, PowerButtonWidgetComponent, + ValueStepperWidgetComponent, SliderWidgetComponent, ToggleButtonWidgetComponent, TimeSeriesChartWidgetComponent, diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts index 1721a7f068..595a8a2914 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts @@ -79,11 +79,11 @@ import { Filter, singleEntityFilterFromDeviceId } from '@shared/models/query/que import { FilterDialogComponent, FilterDialogData } from '@home/components/filter/filter-dialog.component'; import { ToggleHeaderOption } from '@shared/components/toggle-header.component'; import { coerceBoolean } from '@shared/decorators/coercion'; -import { basicWidgetConfigComponentsMap } from '@home/components/widget/config/basic/basic-widget-config.module'; import { TimewindowConfigData } from '@home/components/widget/config/timewindow-config-panel.component'; import { DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models'; import { defaultFormProperties, FormProperty } from '@shared/models/dynamic-form.models'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { WidgetService } from '@core/http/widget.service'; import Timeout = NodeJS.Timeout; @Component({ @@ -205,6 +205,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe public translate: TranslateService, private fb: UntypedFormBuilder, private cd: ChangeDetectorRef, + private widgetService: WidgetService, private destroyRef: DestroyRef) { super(store); } @@ -435,7 +436,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe } private setupBasicModeConfig(isAdd = false) { - const componentType = basicWidgetConfigComponentsMap[this.modelValue.basicModeDirective]; + const componentType = this.widgetService.getBasicWidgetSettingsComponentBySelector(this.modelValue.basicModeDirective); if (!componentType) { this.basicModeDirectiveError = this.translate.instant('widget-config.settings-component-not-found', {selector: this.modelValue.basicModeDirective}); diff --git a/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts b/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts index dbdcdcd61e..7e1a25d61a 100644 --- a/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts +++ b/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts @@ -144,7 +144,8 @@ export class DateEntityTableColumn> extends EntityTabl export class EntityChipsEntityTableColumn> extends BaseEntityTableColumn { constructor(public key: string, public title: string, - public width: string = '0px') { + public width: string = '0px', + public entityURL?: (entity) => string) { super('entityChips', key, title, width, false); } } diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client.component.ts index 70355c0a3e..08d7782a7f 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client.component.ts @@ -203,7 +203,9 @@ export class ClientComponent extends EntityComponent { this.changeMapperConfigType(this.entityForm, value); diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain-table-config.resolver.ts index 8e1014a1da..8048fec885 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain-table-config.resolver.ts @@ -32,7 +32,8 @@ import { DomainComponent } from '@home/pages/admin/oauth2/domains/domain.compone import { isEqual } from '@core/utils'; import { DomainTableHeaderComponent } from '@home/pages/admin/oauth2/domains/domain-table-header.component'; import { Direction } from '@app/shared/models/page/sort-order'; -import { map, mergeMap, Observable, of } from 'rxjs'; +import { map, of } from 'rxjs'; +import { switchMap } from 'rxjs/operators'; @Injectable() export class DomainTableConfigResolver { @@ -84,16 +85,15 @@ export class DomainTableConfigResolver { this.config.loadEntity = id => this.domainService.getDomainInfoById(id.id); this.config.saveEntity = (domain, originalDomain) => { const clientsIds = domain.oauth2ClientInfos as Array || []; - let clientsTask: Observable; - if (domain.id && !isEqual(domain.oauth2ClientInfos?.sort(), - originalDomain.oauth2ClientInfos?.map(info => info.id ? info.id.id : info).sort())) { - clientsTask = this.domainService.updateOauth2Clients(domain.id.id, clientsIds); - } else { - clientsTask = of(null); - } + const shouldUpdateClients = domain.id && !isEqual(domain.oauth2ClientInfos?.sort(), + originalDomain.oauth2ClientInfos?.map(info => info.id ? info.id.id : info).sort()); delete domain.oauth2ClientInfos; - return clientsTask.pipe( - mergeMap(() => this.domainService.saveDomain(domain, domain.id ? [] : clientsIds)), + + return this.domainService.saveDomain(domain, domain.id ? null : clientsIds).pipe( + switchMap(savedDomain => shouldUpdateClients + ? this.domainService.updateOauth2Clients(domain.id.id, clientsIds).pipe(map(() => savedDomain)) + : of(savedDomain) + ), map(savedDomain => { (savedDomain as DomainInfo).oauth2ClientInfos = clientsIds; return savedDomain; @@ -112,12 +112,9 @@ export class DomainTableConfigResolver { $event.stopPropagation(); } - const modifiedDomain: DomainInfo = { - ...domain, - oauth2Enabled: !domain.oauth2Enabled - }; + const { oauth2ClientInfos, oauth2Enabled, ...updatedDomain } = domain; - this.domainService.saveDomain(modifiedDomain, domain.oauth2ClientInfos.map(clientInfo => clientInfo.id.id), + this.domainService.saveDomain({ ...updatedDomain, oauth2Enabled: !oauth2Enabled }, null, {ignoreLoading: true}) .subscribe((result) => { domain.oauth2Enabled = result.oauth2Enabled; @@ -130,12 +127,9 @@ export class DomainTableConfigResolver { $event.stopPropagation(); } - const modifiedDomain: DomainInfo = { - ...domain, - propagateToEdge: !domain.propagateToEdge - }; + const { oauth2ClientInfos, propagateToEdge, ...updatedDomain } = domain; - this.domainService.saveDomain(modifiedDomain, domain.oauth2ClientInfos.map(clientInfo => clientInfo.id.id), + this.domainService.saveDomain({ ...updatedDomain, propagateToEdge: !propagateToEdge }, null, {ignoreLoading: true}) .subscribe((result) => { domain.propagateToEdge = result.propagateToEdge; diff --git a/ui-ngx/src/app/modules/home/pages/admin/queue/queue.component.html b/ui-ngx/src/app/modules/home/pages/admin/queue/queue.component.html index f25cd1c857..68b73e7357 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/queue/queue.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/queue/queue.component.html @@ -22,7 +22,7 @@ [class.!hidden]="isEdit || isDetailsPage"> {{'common.open-details-page' | translate }} - - - - - - -
    diff --git a/ui-ngx/src/app/modules/home/pages/mobile/bundes/layout/custom-mobile-page.component.ts b/ui-ngx/src/app/modules/home/pages/mobile/bundes/layout/custom-mobile-page.component.ts index 3075ae772b..cf57c90a14 100644 --- a/ui-ngx/src/app/modules/home/pages/mobile/bundes/layout/custom-mobile-page.component.ts +++ b/ui-ngx/src/app/modules/home/pages/mobile/bundes/layout/custom-mobile-page.component.ts @@ -24,7 +24,12 @@ import { Validator, Validators } from '@angular/forms'; -import { CustomMobilePage, MobilePageType, mobilePageTypeTranslations } from '@shared/models/mobile-app.models'; +import { + CustomMobilePage, + MobilePageType, + mobilePageTypeTranslations, + WEB_URL_REGEX +} from '@shared/models/mobile-app.models'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { getCurrentAuthUser } from '@core/auth/auth.selectors'; import { Authority } from '@shared/models/authority.enum'; @@ -60,11 +65,11 @@ export class CustomMobilePageComponent implements ControlValueAccessor, Validato customMobilePageForm = this.fb.group({ visible: [true], icon: ['star'], - label: ['', [Validators.required, Validators.pattern(/\S/)]], + label: ['', [Validators.required, Validators.pattern(/\S/), Validators.maxLength(255)]], type: [MobilePageType.DASHBOARD], dashboardId: this.fb.control(null, Validators.required), - url: [{value:'', disabled: true}, [Validators.required, Validators.pattern(/^(https?:\/\/)?(localhost|([\w\-]+\.)+[\w\-]+)(:\d+)?(\/[\w\-._~:\/?#[\]@!$&'()*+,;=%]*)?$/)]], - path: [{value:'', disabled: true}, [Validators.required, Validators.pattern(/^(\/[\w\-._~:\/?#[\]@!$&'()*+,;=%]*)?$/)]] + url: [{value:'', disabled: true}, [Validators.required, Validators.pattern(WEB_URL_REGEX)]], + path: [{value:'', disabled: true}, [Validators.required, Validators.pattern(/^(\/[\w\-._~:/?#[\]@!$&'()*+,;=%]*)?$/)]] }); private propagateChange = (_val: any) => {}; diff --git a/ui-ngx/src/app/modules/home/pages/mobile/bundes/mobile-bundle-dialog.component.html b/ui-ngx/src/app/modules/home/pages/mobile/bundes/mobile-bundle-dialog.component.html index 8e029cf9f7..e3352a2eb4 100644 --- a/ui-ngx/src/app/modules/home/pages/mobile/bundes/mobile-bundle-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/mobile/bundes/mobile-bundle-dialog.component.html @@ -44,6 +44,12 @@ {{ 'mobile.title-required' | translate }} + + {{ 'mobile.title-cannot-contain-only-spaces' | translate }} + + + {{ 'mobile.title-max-length' | translate }} + { - if (afterAdd) { - this.config.updateData(); - } - }); + .subscribe(); }) } } diff --git a/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.html b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.html index ff975cd74f..7add77defc 100644 --- a/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.html +++ b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.html @@ -16,19 +16,19 @@ -->
    - - -
    diff --git a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts index 417fc35bf4..e818e07ee4 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts @@ -14,24 +14,13 @@ /// limitations under the License. /// -import { - AfterViewInit, - Component, - ElementRef, - EventEmitter, - forwardRef, - Input, - OnInit, - Output, - ViewChild -} from '@angular/core'; +import { Component, ElementRef, EventEmitter, forwardRef, Input, OnInit, Output, ViewChild } from '@angular/core'; import { MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form-field'; import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; -import { merge, Observable, of, Subject } from 'rxjs'; +import { firstValueFrom, merge, Observable, of, Subject } from 'rxjs'; import { catchError, debounceTime, map, share, switchMap, tap } from 'rxjs/operators'; import { Store } from '@ngrx/store'; import { AppState } from '@app/core/core.state'; -import { TranslateService } from '@ngx-translate/core'; import { AliasEntityType, EntityType } from '@shared/models/entity-type.models'; import { BaseData } from '@shared/models/base-data'; import { EntityId } from '@shared/models/id/entity-id'; @@ -51,22 +40,22 @@ import { coerceArray, coerceBoolean } from '@shared/decorators/coercion'; multi: true }] }) -export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit, AfterViewInit { +export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit { selectEntityFormGroup: UntypedFormGroup; - modelValue: string | EntityId | null; + private modelValue: string | EntityId | null; - entityTypeValue: EntityType | AliasEntityType; + private entityTypeValue: EntityType | AliasEntityType; - entitySubtypeValue: string; + private entitySubtypeValue: string; - entityText: string; + private entityText: string; noEntitiesMatchingText: string; notFoundEntities = 'entity.no-entities-text'; - entityRequiredText: string; + private entityRequiredText: string; filteredEntities: Observable>>; @@ -78,7 +67,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit private refresh$ = new Subject>>(); - private propagateChange = (v: any) => { }; + private propagateChange: (value: any) => void = () => { }; @Input() set entityType(entityType: EntityType) { @@ -166,7 +155,6 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit constructor(private store: Store, - public translate: TranslateService, private entityService: EntityService, private fb: UntypedFormBuilder) { this.selectEntityFormGroup = this.fb.group({ @@ -178,7 +166,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit this.propagateChange = fn; } - registerOnTouched(fn: any): void { + registerOnTouched(_fn: any): void { } ngOnInit() { @@ -188,7 +176,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit .pipe( debounceTime(150), tap(value => { - let modelValue; + let modelValue: string | EntityId; if (typeof value === 'string' || !value) { modelValue = null; } else { @@ -207,9 +195,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit ); } - ngAfterViewInit(): void {} - - load(): void { + private load(): void { if (this.entityTypeValue) { switch (this.entityTypeValue) { case EntityType.ASSET: @@ -327,7 +313,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit } } - getCurrentEntity(): BaseData | null { + private getCurrentEntity(): BaseData | null { const currentEntity = this.selectEntityFormGroup.get('entity').value; if (currentEntity && typeof currentEntity !== 'string') { return currentEntity as BaseData; @@ -359,16 +345,17 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit } let entity: BaseData = null; try { - entity = await this.entityService.getEntity(targetEntityType, id, {ignoreLoading: true, ignoreErrors: true}).toPromise(); + entity = await firstValueFrom(this.entityService.getEntity(targetEntityType, id, {ignoreLoading: true, ignoreErrors: true})); } catch (e) { this.propagateChange(null); } this.modelValue = entity !== null ? (this.useFullEntityId ? entity.id : entity.id.id) : null; - this.entityURL = getEntityDetailsPageURL(this.modelValue as string, targetEntityType); + this.entityURL = !entity ? '' : getEntityDetailsPageURL(entity.id.id, targetEntityType); this.selectEntityFormGroup.get('entity').patchValue(entity !== null ? entity : '', {emitEvent: false}); this.entityChanged.emit(entity); } else { this.modelValue = null; + this.entityURL = ''; this.selectEntityFormGroup.get('entity').patchValue('', {emitEvent: false}); } this.dirty = true; @@ -381,13 +368,14 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit } } - reset() { + private reset() { this.selectEntityFormGroup.get('entity').patchValue('', {emitEvent: false}); } - updateView(value: string | null, entity: BaseData | null) { + private updateView(value: string | EntityId | null, entity: BaseData | null) { if (!isEqual(this.modelValue, value)) { this.modelValue = value; + this.entityURL = !entity ? '' : getEntityDetailsPageURL(entity.id.id, entity.id.entityType as EntityType); this.propagateChange(this.modelValue); this.entityChanged.emit(entity); } @@ -397,7 +385,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit return entity ? entity.name : undefined; } - fetchEntities(searchText?: string): Observable>> { + private fetchEntities(searchText?: string): Observable>> { this.searchText = searchText; const targetEntityType = this.checkEntityType(this.entityTypeValue); return this.entityService.getEntitiesByNameFilter(targetEntityType, searchText, @@ -432,7 +420,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit }, 0); } - checkEntityType(entityType: EntityType | AliasEntityType): EntityType { + private checkEntityType(entityType: EntityType | AliasEntityType): EntityType { if (entityType === AliasEntityType.CURRENT_CUSTOMER) { return EntityType.CUSTOMER; } else if (entityType === AliasEntityType.CURRENT_TENANT) { @@ -454,4 +442,8 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit $event.stopPropagation(); this.createNew.emit(); } + + get showEntityLink(): boolean { + return this.selectEntityFormGroup.get('entity').value && this.disabled && this.entityURL !== ''; + } } diff --git a/ui-ngx/src/app/shared/components/js-func-modules.component.html b/ui-ngx/src/app/shared/components/js-func-modules.component.html index 7aa70afd1d..b011838212 100644 --- a/ui-ngx/src/app/shared/components/js-func-modules.component.html +++ b/ui-ngx/src/app/shared/components/js-func-modules.component.html @@ -27,7 +27,7 @@
    - diff --git a/ui-ngx/src/app/shared/components/time/datetime-period.component.html b/ui-ngx/src/app/shared/components/time/datetime-period.component.html index 84f6d29d68..03c5a6ac21 100644 --- a/ui-ngx/src/app/shared/components/time/datetime-period.component.html +++ b/ui-ngx/src/app/shared/components/time/datetime-period.component.html @@ -15,17 +15,17 @@ limitations under the License. --> -
    +
    datetime.from - + datetime.to - +
    diff --git a/ui-ngx/src/app/shared/components/time/datetime-period.component.ts b/ui-ngx/src/app/shared/components/time/datetime-period.component.ts index c0fbaea21e..0e212f7630 100644 --- a/ui-ngx/src/app/shared/components/time/datetime-period.component.ts +++ b/ui-ngx/src/app/shared/components/time/datetime-period.component.ts @@ -14,10 +14,17 @@ /// limitations under the License. /// -import { Component, forwardRef, Input, OnInit } from '@angular/core'; -import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { FixedWindow } from '@shared/models/time/time.models'; +import { Component, forwardRef, Input } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { DAY, FixedWindow, MINUTE } from '@shared/models/time/time.models'; import { MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form-field'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { distinctUntilChanged } from 'rxjs/operators'; + +interface DateTimePeriod { + startDate: Date; + endDate: Date; +} @Component({ selector: 'tb-datetime-period', @@ -31,7 +38,7 @@ import { MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form- } ] }) -export class DatetimePeriodComponent implements OnInit, ControlValueAccessor { +export class DatetimePeriodComponent implements ControlValueAccessor { @Input() disabled: boolean; @@ -41,25 +48,45 @@ export class DatetimePeriodComponent implements OnInit, ControlValueAccessor { @Input() appearance: MatFormFieldAppearance = 'fill'; - modelValue: FixedWindow; - - startDate: Date; - endDate: Date; - - endTime: any; + private modelValue: FixedWindow; maxStartDate: Date; - minEndDate: Date; maxEndDate: Date; - changePending = false; + private maxStartDateTs: number; + private minEndDateTs: number; + private maxStartTs: number; + private maxEndTs: number; - private propagateChange = null; + private timeShiftMs = MINUTE; - constructor() { - } + dateTimePeriodFormGroup = this.fb.group({ + startDate: this.fb.control(null), + endDate: this.fb.control(null) + }); + + private changePending = false; - ngOnInit(): void { + private propagateChange = null; + + constructor(private fb: FormBuilder) { + this.dateTimePeriodFormGroup.valueChanges.pipe( + distinctUntilChanged((prevDateTimePeriod, dateTimePeriod) => + prevDateTimePeriod.startDate === dateTimePeriod.startDate && prevDateTimePeriod.endDate === dateTimePeriod.endDate), + takeUntilDestroyed() + ).subscribe((dateTimePeriod: DateTimePeriod) => { + this.updateMinMaxDates(dateTimePeriod); + this.updateView(); + }); + + this.dateTimePeriodFormGroup.get('startDate').valueChanges.pipe( + distinctUntilChanged(), + takeUntilDestroyed() + ).subscribe(startDate => this.onStartDateChange(startDate)); + this.dateTimePeriodFormGroup.get('endDate').valueChanges.pipe( + distinctUntilChanged(), + takeUntilDestroyed() + ).subscribe(endDate => this.onEndDateChange(endDate)); } registerOnChange(fn: any): void { @@ -75,35 +102,38 @@ export class DatetimePeriodComponent implements OnInit, ControlValueAccessor { setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; + if (this.disabled) { + this.dateTimePeriodFormGroup.disable({emitEvent: false}); + } else { + this.dateTimePeriodFormGroup.enable({emitEvent: false}); + } } writeValue(datePeriod: FixedWindow): void { this.modelValue = datePeriod; if (this.modelValue) { - this.startDate = new Date(this.modelValue.startTimeMs); - this.endDate = new Date(this.modelValue.endTimeMs); + this.dateTimePeriodFormGroup.patchValue({ + startDate: new Date(this.modelValue.startTimeMs), + endDate: new Date(this.modelValue.endTimeMs) + }, {emitEvent: false}); } else { const date = new Date(); - this.startDate = new Date( - date.getFullYear(), - date.getMonth(), - date.getDate() - 1, - date.getHours(), - date.getMinutes(), - date.getSeconds(), - date.getMilliseconds()); - this.endDate = date; + this.dateTimePeriodFormGroup.patchValue({ + startDate: new Date(date.getTime() - DAY), + endDate: date + }, {emitEvent: false}); this.updateView(); } - this.updateMinMaxDates(); + this.updateMinMaxDates(this.dateTimePeriodFormGroup.value); } - updateView() { + private updateView() { let value: FixedWindow = null; - if (this.startDate && this.endDate) { + const dateTimePeriod = this.dateTimePeriodFormGroup.value; + if (dateTimePeriod.startDate && dateTimePeriod.endDate) { value = { - startTimeMs: this.startDate.getTime(), - endTimeMs: this.endDate.getTime() + startTimeMs: dateTimePeriod.startDate.getTime(), + endTimeMs: dateTimePeriod.endDate.getTime() }; } this.modelValue = value; @@ -114,32 +144,40 @@ export class DatetimePeriodComponent implements OnInit, ControlValueAccessor { } } - updateMinMaxDates() { - this.maxStartDate = new Date(this.endDate.getTime() - 1000); - this.minEndDate = new Date(this.startDate.getTime() + 1000); + private updateMinMaxDates(dateTimePeriod: Partial) { this.maxEndDate = new Date(); + this.maxEndTs = this.maxEndDate.getTime(); + this.maxStartTs = this.maxEndTs - this.timeShiftMs; + this.maxStartDate = new Date(this.maxStartTs); + + if (dateTimePeriod.endDate) { + this.maxStartDateTs = dateTimePeriod.endDate.getTime() - this.timeShiftMs; + } + if (dateTimePeriod.startDate) { + this.minEndDateTs = dateTimePeriod.startDate.getTime() + this.timeShiftMs; + } } - onStartDateChange() { - if (this.startDate) { - if (this.startDate.getTime() > this.maxStartDate.getTime()) { - this.startDate = new Date(this.maxStartDate.getTime()); + private onStartDateChange(startDate: Date) { + if (startDate) { + if (startDate.getTime() > this.maxStartTs) { + this.dateTimePeriodFormGroup.get('startDate').patchValue(new Date(this.maxStartTs), { emitEvent: false }); + } + if (startDate.getTime() > this.maxStartDateTs) { + this.dateTimePeriodFormGroup.get('endDate').patchValue(new Date(startDate.getTime() + this.timeShiftMs), { emitEvent: false }); } - this.updateMinMaxDates(); } - this.updateView(); } - onEndDateChange() { - if (this.endDate) { - if (this.endDate.getTime() < this.minEndDate.getTime()) { - this.endDate = new Date(this.minEndDate.getTime()); - } else if (this.endDate.getTime() > this.maxEndDate.getTime()) { - this.endDate = new Date(this.maxEndDate.getTime()); + private onEndDateChange(endDate: Date) { + if (endDate) { + if (endDate.getTime() > this.maxEndTs) { + this.dateTimePeriodFormGroup.get('endDate').patchValue(new Date(this.maxEndTs), { emitEvent: false }); + } + if (endDate.getTime() < this.minEndDateTs) { + this.dateTimePeriodFormGroup.get('startDate').patchValue(new Date(endDate.getTime() - this.timeShiftMs), { emitEvent: false }); } - this.updateMinMaxDates(); } - this.updateView(); } } diff --git a/ui-ngx/src/app/shared/import-export/import-dialog.component.ts b/ui-ngx/src/app/shared/import-export/import-dialog.component.ts index e4256229c4..7e46f47978 100644 --- a/ui-ngx/src/app/shared/import-export/import-dialog.component.ts +++ b/ui-ngx/src/app/shared/import-export/import-dialog.component.ts @@ -71,7 +71,7 @@ export class ImportDialogComponent extends DialogComponent= 0) { + if (result.search(/([",;\n])/g) >= 0) { result = `"${result}"`; } return result; diff --git a/ui-ngx/src/app/shared/models/action-widget-settings.models.ts b/ui-ngx/src/app/shared/models/action-widget-settings.models.ts index b1c47a1fd3..fd78ab30f0 100644 --- a/ui-ngx/src/app/shared/models/action-widget-settings.models.ts +++ b/ui-ngx/src/app/shared/models/action-widget-settings.models.ts @@ -25,7 +25,8 @@ export enum GetValueAction { GET_ATTRIBUTE = 'GET_ATTRIBUTE', GET_TIME_SERIES = 'GET_TIME_SERIES', GET_ALARM_STATUS = 'GET_ALARM_STATUS', - GET_DASHBOARD_STATE = 'GET_DASHBOARD_STATE' + GET_DASHBOARD_STATE = 'GET_DASHBOARD_STATE', + GET_DASHBOARD_STATE_OBJECT = 'GET_DASHBOARD_STATE_OBJECT', } export const getValueActions = Object.keys(GetValueAction) as GetValueAction[]; @@ -45,7 +46,8 @@ export const getValueActionTranslations = new Map( [GetValueAction.GET_ATTRIBUTE, 'widgets.value-action.get-attribute'], [GetValueAction.GET_TIME_SERIES, 'widgets.value-action.get-time-series'], [GetValueAction.GET_ALARM_STATUS, 'widgets.value-action.get-alarm-status'], - [GetValueAction.GET_DASHBOARD_STATE, 'widgets.value-action.get-dashboard-state'] + [GetValueAction.GET_DASHBOARD_STATE, 'widgets.value-action.get-dashboard-state'], + [GetValueAction.GET_DASHBOARD_STATE_OBJECT, 'widgets.value-action.get-dashboard-state-object'], ] ); diff --git a/ui-ngx/src/app/shared/models/mobile-app.models.ts b/ui-ngx/src/app/shared/models/mobile-app.models.ts index acf609a637..7286910adc 100644 --- a/ui-ngx/src/app/shared/models/mobile-app.models.ts +++ b/ui-ngx/src/app/shared/models/mobile-app.models.ts @@ -21,6 +21,8 @@ import { OAuth2ClientInfo, PlatformType } from '@shared/models/oauth2.models'; import { MobileAppBundleId } from '@shared/models/id/mobile-app-bundle-id'; import { deepClone, isNotEmptyStr } from '@core/utils'; +export const WEB_URL_REGEX = /^(https?:\/\/)?(localhost|([\p{L}\p{M}\w-]+\.)+[\p{L}\p{M}\w-]+)(:\d+)?(\/[\w\-._~:/?#[\]@!$&'()*+,;=%\p{L}\p{N}]*)?$/u; + export interface QrCodeSettings extends HasTenantId { useDefaultApp: boolean; mobileAppBundleId: MobileAppBundleId diff --git a/ui-ngx/src/app/shared/models/oauth2.models.ts b/ui-ngx/src/app/shared/models/oauth2.models.ts index abc0ae1464..19a95a9788 100644 --- a/ui-ngx/src/app/shared/models/oauth2.models.ts +++ b/ui-ngx/src/app/shared/models/oauth2.models.ts @@ -80,11 +80,7 @@ export interface Domain extends BaseData, HasTenantId { propagateToEdge: boolean; } -export interface HasOauth2Clients { - oauth2ClientInfos?: Array | Array; -} - -export interface DomainInfo extends Domain, HasOauth2Clients { +export interface DomainInfo extends Domain { oauth2ClientInfos?: Array | Array; } diff --git a/ui-ngx/src/app/shared/models/rule-node.models.ts b/ui-ngx/src/app/shared/models/rule-node.models.ts index a79fdbc376..471594f689 100644 --- a/ui-ngx/src/app/shared/models/rule-node.models.ts +++ b/ui-ngx/src/app/shared/models/rule-node.models.ts @@ -21,9 +21,7 @@ import { ComponentDescriptor } from '@shared/models/component-descriptor.models' import { FcEdge, FcNode } from 'ngx-flowchart'; import { Observable } from 'rxjs'; import { PageComponent } from '@shared/components/page.component'; -import { AfterViewInit, EventEmitter, Inject, OnInit, Directive, DestroyRef, inject } from '@angular/core'; -import { Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; +import { AfterViewInit, DestroyRef, Directive, EventEmitter, inject, OnInit } from '@angular/core'; import { AbstractControl, UntypedFormGroup } from '@angular/forms'; import { RuleChainType } from '@shared/models/rule-chain.models'; import { DebugRuleNodeEventBody } from '@shared/models/event.models'; @@ -103,7 +101,7 @@ export abstract class RuleNodeConfigurationComponent extends PageComponent imple private configurationSet = false; private disabledValue = false; - private destroyRef = inject(DestroyRef); + protected destroyRef = inject(DestroyRef); set disabled(value: boolean) { if (this.disabledValue !== value) { @@ -134,8 +132,8 @@ export abstract class RuleNodeConfigurationComponent extends PageComponent imple configurationChangedEmiter = new EventEmitter(); configurationChanged = this.configurationChangedEmiter.asObservable(); - protected constructor(@Inject(Store) protected store: Store) { - super(store); + protected constructor(...args: unknown[]) { + super(); } ngOnInit() {} diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index 5950dcf911..6e45e8705a 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -41,7 +41,7 @@ import { isNotEmptyStr, mergeDeepIgnoreArray } from '@core/utils'; import { WidgetConfigComponentData } from '@home/models/widget-component.models'; import { ComponentStyle, Font, TimewindowStyle } from '@shared/models/widget-settings.models'; import { NULL_UUID } from '@shared/models/id/has-uuid'; -import { HasTenantId, HasVersion } from '@shared/models/entity.models'; +import { EntityInfoData, HasTenantId, HasVersion } from '@shared/models/entity.models'; import { DataKeysCallbacks, DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models'; import { WidgetConfigCallbacks } from '@home/components/widget/config/widget-config.component.models'; import { TbFunction } from '@shared/models/js-function.models'; @@ -270,6 +270,7 @@ export interface WidgetTypeInfo extends BaseWidgetType { description: string; tags: string[]; widgetType: widgetType; + bundles?: EntityInfoData[]; } export interface WidgetTypeDetails extends WidgetType, ExportableEntity { diff --git a/ui-ngx/src/assets/help/en_US/rulenode/save_timeseries_node_advanced.md b/ui-ngx/src/assets/help/en_US/rulenode/save_timeseries_node_advanced.md new file mode 100644 index 0000000000..44a037f180 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/rulenode/save_timeseries_node_advanced.md @@ -0,0 +1,29 @@ +#### Potential unexpected behavior with mixed processing strategies + +When configuring the processing strategies, certain combinations can lead to unexpected behavior. Consider the following scenarios: + +- **Disabling WebSocket (WS) updates** + + If WS updates are disabled, any changes to the time series data won’t be pushed to dashboards (or other WS subscriptions). + This means that even if a database is updated, dashboards may not display the updated data until browser page is reloaded. + +- **Different deduplication intervals across actions** + + When you configure different deduplication intervals for actions, the same incoming message might be processed differently for each action. + For example, a message might be stored immediately in the Time series table (if set to *On every message*) while not being stored in the Latest values table because its deduplication interval hasn’t elapsed. + Also, if the WebSocket updates are configured with a different interval, dashboards might show updates that do not match what is stored in the database. + +- **Skipping database storage** + + Choosing to disable one or more persistence actions (for instance, skipping database storage for Time series or Latest values while keeping WS updates enabled) introduces the risk of having only partial data available: + - If a message is processed only for real-time notifications (WebSockets) and not stored in the database, historical queries may not match data on the dashboard. + - When processing strategies for Time series and Latest values are out-of-sync, telemetry data may be stored in one table (e.g., Time series) while the same data is absent in the other (e.g., Latest values). + +- **Deduplication cache clearing** + + The deduplication mechanism uses a cache to track processed messages within each interval. + For performance and system stability reasons, this cache is periodically cleared. + As a result, if a cache entry is removed during the deduplication period, messages from the same originator may be processed more than once within that interval. + This means deduplication should be used as a performance optimization rather than an absolute guarantee of single processing per interval. + +We recommend using deduplication only when the occasional repeated processing is acceptable and won't cause system correctness issue or data inconsistencies. diff --git a/ui-ngx/src/assets/help/en_US/widget/config/parse_value_get_dashboard_state_object_fn.md b/ui-ngx/src/assets/help/en_US/widget/config/parse_value_get_dashboard_state_object_fn.md new file mode 100644 index 0000000000..ddc1ed7bc8 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/widget/config/parse_value_get_dashboard_state_object_fn.md @@ -0,0 +1,40 @@ +#### Parse value function + +
    +
    + +*function (data): boolean* + +A JavaScript function that converts the current dashboard state object into a boolean value. + +**Parameters:** + +
      +
    • data: StateObject - the current dashboard state object. +
    • +
    + +**Returns:** + +`true` if the widget should be in an activated state, `false` otherwise. + +
    + +##### Examples + +* Check if the current dashboard state id is "default": + +```javascript +return data.id === 'default' ? true : false; +{:copy-code} +``` + +* Check if the current dashboard state parameters are empty: + +```javascript +return Object.keys(data.params).length ? true : false; +{:copy-code} +``` + +
    +
    diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index a584bb453b..18801bda2c 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -3218,6 +3218,7 @@ "bottom-fluid-color": "Bottom fluid color", "display": "Display", "value": "Value", + "decimals": "Decimals", "units": "Units", "flow-meter-value-hint": "Double value showing on flow meter display", "value-hint": "Double value indicating the current value", @@ -3250,8 +3251,11 @@ "rotation-animation-speed-hint": "Double value indicating rotation animation speed. 1 - normal speed, 0 - no animation, < 1 - slower animation, > 1 - faster animation.", "on-click": "On click", "on-click-hint": "Action invoked when user clicks on the component.", + "connectors-positions": "Connectors positions", + "right-connector": "Right connector", "right-top-connector": "Right top connector", "right-bottom-connector": "Right bottom connector", + "left-connector": "Left connector", "left-top-connector": "Left top connector", "left-bottom-connector": "Left bottom connector", "top-left-connector": "Top left connector", @@ -3363,6 +3367,63 @@ "low-critical-state-hint": "Double value indicates a low critical range up to the min value scale.", "filter-color": "Filter color", "colors": "Colors", + "indicator-colors": "Indicator colors", + "enabled": "Enabled", + "disabled": "Disabled", + "on": "ON", + "off": "OFF", + "on-off-state": "On/Off state", + "on-off-state-hint": "Indicates whether the component is in the On or Off state.", + "on-update-state": "On update state", + "on-update-state-hint": "Action invoked when the user clicks to update the state to On.", + "off-update-state": "Off update state", + "off-update-state-hint": "Action invoked when the user clicks to update the state to Off.", + "voltage": "Voltage", + "input-voltage": "Input voltage", + "input-voltage-hint": "Double value indicates input voltage value.", + "output-voltage": "Output voltage", + "output-voltage-hint": "Double value indicates output voltage value.", + "first-phase-voltage": "First phase voltage", + "second-phase-voltage": "Second phase voltage", + "third-phase-voltage": "Third phase voltage", + "phase-voltage-hint": "Double value indicates voltage value for current phase", + "voltage-hint": "Double value indicates current voltage", + "current-voltage-color": "Current voltage color", + "phase-indicator-color": "Phase indicator color", + "measured": "Measured", + "measured-hint": "Double value indicates energy usage in kilowatt-hours", + "day-rate": "Day rate", + "night-rate": "Night rate", + "off-peak-rate": "Off-peak rate", + "peak-rate": "Peak rate", + "export-rate": "Export rate", + "operating-mode": "Operating mode", + "bypass-mode": "Bypass", + "operating-mode-hint": "Integer value indication the current operating mode (0 - OFF, 1 - ON, 2 - BYPASS)", + "connected": "Connected", + "connected-hint": "Indicates whether component is in connected state.", + "disconnected": "Disconnected", + "indicator": "Indicator", + "operation-mode": "Operation mode", + "operation-mode-hint": "Indicates whether inverter is in Mains or Inverter mode.", + "operation-mode-indicators-color": "Operation mode indicators color", + "mains-on-mode": "Mains on", + "inverter-on-mode": "Inverter on", + "charging-mode": "Charging mode", + "charging-mode-hint": "Integer value indication the current charging mode (1 - Bulk, 2 - Absorption, 3 - Float)", + "charging-mode-indicators-color": "Charging mode indicators color", + "inverter-faults": "Faults", + "inverter-fault-indicators-color": "Fault indicators color", + "overload-fault": "Overload", + "overload-fault-hint": "Indicates whether inverter is in overload condition.", + "low-battery-fault": "Low battery", + "low-battery-fault-hint": "Indicates whether battery is excessively discharged.", + "temperature-fault": "Temperature", + "temperature-fault-hint": "Indicates whether high temperature in an inverter.", + "triangle": "Triangle", + "socket": "Socket", + "left-button": "Left button", + "right-button": "Right button", "alarm-colors": "Alarm colors", "hook-color": "Hook color" } @@ -3580,6 +3641,8 @@ "add-bundle": "Add bundle", "title": "Title", "title-required": "Title is required", + "title-cannot-contain-only-spaces": "Title cannot contain only spaces", + "title-max-length": "Title should be less than 256", "oauth-clients": "OAuth 2.0 clients", "android-app": "Android App", "android-application": "Android Application", @@ -3611,6 +3674,7 @@ "page-name": "Page name", "page-name-required": "Page name is required.", "page-name-cannot-contain-only-spaces": "Page name cannot contain only spaces.", + "page-name-max-length": "Page name should be less than 256", "page-type": "Page type", "pages-types": { "dashboard": "Dashboard", @@ -4362,6 +4426,760 @@ "queue-hint": "Select a queue for message forwarding to another queue. 'Main' queue is used by default.", "queue-singleton-hint": "Select a queue for message forwarding in multi-instance environments. 'Main' queue is used by default." }, + "rule-node-config": { + "id": "Id", + "additional-info": "Additional Info", + "advanced-settings": "Advanced settings", + "create-entity-if-not-exists": "Create new entity if it doesn't exist", + "create-entity-if-not-exists-hint": "If enabled, a new entity with specified parameters will be created unless it already exists. Existing entities will be used as is for relation.", + "select-device-connectivity-event": "Select device connectivity event", + "entity-name-pattern": "Name pattern", + "device-name-pattern": "Device name", + "asset-name-pattern": "Asset name", + "entity-view-name-pattern": "Entity view name", + "customer-title-pattern": "Customer title", + "dashboard-name-pattern": "Dashboard title", + "user-name-pattern": "User email", + "edge-name-pattern": "Edge name", + "entity-name-pattern-required": "Name pattern is required", + "entity-name-pattern-hint": "Name pattern field support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.", + "copy-message-type": "Copy message type", + "entity-type-pattern": "Type pattern", + "entity-type-pattern-required": "Type pattern is required", + "message-type-value": "Message type value", + "message-type-value-required": "Message type value is required", + "message-type-value-max-length": "Message type value should be less than 256", + "output-message-type": "Output message type", + "entity-cache-expiration": "Entities cache expiration time (sec)", + "entity-cache-expiration-hint": "Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.", + "entity-cache-expiration-required": "Entities cache expiration time is required.", + "entity-cache-expiration-range": "Entities cache expiration time should be greater than or equal to 0.", + "customer-name-pattern": "Customer title", + "customer-name-pattern-required": "Customer title is required", + "customer-name-pattern-hint": "Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.", + "create-customer-if-not-exists": "Create new customer if it doesn't exist", + "unassign-from-customer": "Unassign from specific customer if originator is dashboard", + "unassign-from-customer-tooltip": "Only dashboards can be assigned to multiple customers at once. \nIf the message originator is a dashboard, you need to explicitly specify the customer's title to unassign from.", + "customer-cache-expiration": "Customers cache expiration time (sec)", + "customer-cache-expiration-hint": "Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.", + "customer-cache-expiration-required": "Customers cache expiration time is required.", + "customer-cache-expiration-range": "Customers cache expiration time should be greater than or equal to 0.", + "interval-start": "Interval start", + "interval-end": "Interval end", + "time-unit": "Time unit", + "fetch-mode": "Fetch mode", + "order-by-timestamp": "Order by timestamp", + "limit": "Limit", + "limit-hint": "Min limit value is 2, max - 1000. If you want to fetch a single entry, select fetch mode 'First' or 'Last'.", + "limit-required": "Limit is required.", + "limit-range": "Limit should be in a range from 2 to 1000.", + "time-unit-milliseconds": "Milliseconds", + "time-unit-seconds": "Seconds", + "time-unit-minutes": "Minutes", + "time-unit-hours": "Hours", + "time-unit-days": "Days", + "time-value-range": "Allowing range from 1 to 2147483647.", + "start-interval-value-required": "Interval start is required.", + "end-interval-value-required": "Interval end is required.", + "filter": "Filter", + "switch": "Switch", + "math-templatization-tooltip": "This field support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.", + "add-message-type": "Add message type", + "select-message-types-required": "At least one message type should be selected.", + "select-message-types": "Select message types", + "no-message-types-found": "No message types found", + "no-message-type-matching": "'{{messageType}}' not found.", + "create-new-message-type": "Create a new one.", + "message-types-required": "Message types are required.", + "client-attributes": "Client attributes", + "shared-attributes": "Shared attributes", + "server-attributes": "Server attributes", + "attributes-keys": "Attributes keys", + "attributes-keys-required": "Attributes keys are required", + "attributes-scope": "Attributes scope", + "attributes-scope-value": "Attributes scope value", + "attributes-scope-value-copy": "Copy attributes scope value", + "attributes-scope-hint": "Use the 'scope' metadata key to dynamically set the attribute scope per message. If provided, this overrides the scope set in the configuration.", + "notify-device": "Force notification to the device", + "send-attributes-updated-notification": "Send attributes updated notification", + "send-attributes-updated-notification-hint": "Send notification about updated attributes as a separate message to the rule engine queue.", + "send-attributes-deleted-notification": "Send attributes deleted notification", + "send-attributes-deleted-notification-hint": "Send notification about deleted attributes as a separate message to the rule engine queue.", + "update-attributes-only-on-value-change": "Save attributes only if the value changes", + "update-attributes-only-on-value-change-hint": "Updates the attributes on every incoming message disregarding if their value has changed. Increases API usage and reduces performance.", + "update-attributes-only-on-value-change-hint-enabled": "Updates the attributes only if their value has changed. If the value is not changed, no update to the attribute timestamp nor attribute change notification will be sent.", + "fetch-credentials-to-metadata": "Fetch credentials to metadata", + "notify-device-on-update-hint": "If enabled, force notification to the device about shared attributes update. If disabled, the notification behavior is controlled by the 'notifyDevice' parameter from the incoming message metadata. To turn off the notification, the message metadata must contain the 'notifyDevice' parameter set to 'false'. Any other case will trigger the notification to the device.", + "notify-device-on-delete-hint": "If enabled, force notification to the device about shared attributes removal. If disabled, the notification behavior is controlled by the 'notifyDevice' parameter from the incoming message metadata. To turn on the notification, the message metadata must contain the 'notifyDevice' parameter set to 'true'. In any other case, the notification will not be triggered to the device.", + "latest-timeseries": "Latest time series data keys", + "timeseries-keys": "Time series keys", + "timeseries-keys-required": "At least one time series key should be selected.", + "add-timeseries-key": "Add time series key", + "add-message-field": "Add message field", + "relation-search-parameters": "Relation search parameters", + "relation-parameters": "Relation parameters", + "add-metadata-field": "Add metadata field", + "data-keys": "Message field names", + "copy-from": "Copy from", + "data-to-metadata": "Data to metadata", + "metadata-to-data": "Metadata to data", + "use-regular-expression-hint": "Use regular expression to copy keys by pattern.\n\nTips & tricks:\nPress 'Enter' to complete field name input.\nPress 'Backspace' to delete field name. Multiple field names supported.", + "interval": "Interval", + "interval-required": "Interval is required", + "interval-hint": "Deduplication interval in seconds.", + "interval-min-error": "Min allowed value is 1", + "max-pending-msgs": "Max pending messages", + "max-pending-msgs-hint": "Maximum number of messages that are stored in memory for each unique deduplication id.", + "max-pending-msgs-required": "Max pending messages is required", + "max-pending-msgs-max-error": "Max allowed value is 1000", + "max-pending-msgs-min-error": "Min allowed value is 1", + "max-retries": "Max retries", + "max-retries-required": "Max retries is required", + "max-retries-hint": "Maximum number of retries to push the deduplicated messages into the queue. 10 seconds delay is used between retries", + "max-retries-max-error": "Max allowed value is 100", + "max-retries-min-error": "Min allowed value is 0", + "strategy": "Strategy", + "strategy-required": "Strategy is required", + "strategy-all-hint": "Return all messages that arrived during deduplication period as a single JSON array message. Where each element represents object with msg and metadata inner properties.", + "strategy-first-hint": "Return first message that arrived during deduplication period.", + "strategy-last-hint": "Return last message that arrived during deduplication period.", + "first": "First", + "last": "Last", + "all": "All", + "output-msg-type-hint": "The message type of the deduplication result.", + "queue-name-hint": "The queue name where the deduplication result will be published.", + "keys": "Keys", + "keys-required": "Keys are required", + "rename-keys-in": "Rename keys in", + "data": "Data", + "message": "Message", + "metadata": "Metadata", + "current-key-name": "Current key name", + "key-name-required": "Key name is required", + "new-key-name": "New key name", + "new-key-name-required": "New key name is required", + "metadata-keys": "Metadata field names", + "json-path-expression": "JSON path expression", + "json-path-expression-required": "JSON path expression is required", + "json-path-expression-hint": "JSONPath specifies a path to an element or a set of elements in a JSON structure. '$' represents the root object or array.", + "relations-query": "Relations query", + "device-relations-query": "Device relations query", + "max-relation-level": "Max relation level", + "max-relation-level-error": "Value should be greater than 0 or unspecified.", + "max-relation-level-invalid": "Value should be an integer.", + "relation-type": "Relation type", + "relation-type-pattern": "Relation type pattern", + "relation-type-pattern-required": "Relation type pattern is required", + "relation-types-list": "Relation types to propagate", + "relation-types-list-hint": "If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.", + "unlimited-level": "Unlimited level", + "latest-telemetry": "Latest telemetry", + "add-telemetry-key": "Add telemetry key", + "delete-from": "Delete from", + "use-regular-expression-delete-hint": "Use regular expression to delete keys by pattern.\n\nTips & tricks:\nPress 'Enter' to complete field name input.\nPress 'Backspace' to delete field name.\nMultiple field names supported.", + "fetch-into": "Fetch into", + "attr-mapping": "Attributes mapping:", + "source-attribute": "Source attribute key", + "source-attribute-required": "Source attribute key is required.", + "source-telemetry": "Source telemetry key", + "source-telemetry-required": "Source telemetry key is required.", + "target-key": "Target key", + "target-key-required": "Target key is required.", + "attr-mapping-required": "At least one mapping entry should be specified.", + "fields-mapping": "Fields mapping", + "fields-mapping-hint": "If the message field is set to $entityId, the message originator's id will be saved to the specified table column.", + "relations-query-config-direction-suffix": "originator", + "profile-name": "Profile name", + "fetch-circle-parameter-info-from-metadata-hint": "Metadata field '{{perimeterKeyName}}' should be defined in next format: {\"latitude\":48.196, \"longitude\":24.6532, \"radius\":100.0, \"radiusUnit\":\"METER\"}", + "fetch-poligon-parameter-info-from-metadata-hint": "Metadata field '{{perimeterKeyName}}' should be defined in next format: [[48.19736,24.65235],[48.19800,24.65060],...,[48.19849,24.65420]]", + "short-templatization-tooltip": "Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.", + "fields-mapping-required": "At least one field mapping should be specified.", + "at-least-one-field-required": "At least one input field must have a value(s) provided.", + "originator-fields-sv-map-hint": "Target key fields support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.", + "sv-map-hint": "Only target key fields support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.", + "source-field": "Source field", + "source-field-required": "Source field is required.", + "originator-source": "Originator source", + "new-originator": "New originator", + "originator-customer": "Customer", + "originator-tenant": "Tenant", + "originator-related": "Related entity", + "originator-alarm-originator": "Alarm Originator", + "originator-entity": "Entity by name pattern", + "clone-message": "Clone message", + "transform": "Transform", + "default-ttl": "Default TTL", + "default-ttl-required": "Default TTL is required.", + "default-ttl-hint": "Rule node will fetch Time-to-Live (TTL) value from the message metadata. If no value is present, it defaults to the TTL specified in the configuration. If the value is set to 0, the TTL from the tenant profile configuration will be applied.", + "default-ttl-zero-hint": "TTL will not be applied if its value is set to 0.", + "min-default-ttl-message": "Only 0 minimum TTL is allowed.", + "generation-parameters": "Generation parameters", + "message-count": "Generated messages limit (0 - unlimited)", + "message-count-required": "Generated messages limit is required.", + "min-message-count-message": "Only 0 minimum message count is allowed.", + "period-seconds": "Period in seconds", + "period-seconds-required": "Period is required.", + "generation-frequency-seconds": "Generation frequency in seconds", + "generation-frequency-required": "Generation frequency is required.", + "min-generation-frequency-message": "Only 1 second minimum is allowed.", + "script-lang-tbel": "TBEL", + "script-lang-js": "JS", + "use-metadata-period-in-seconds-patterns": "Use period in seconds pattern", + "use-metadata-period-in-seconds-patterns-hint": "If selected, rule node use period in seconds interval pattern from message metadata or data assuming that intervals are in the seconds.", + "period-in-seconds-pattern": "Period in seconds pattern", + "period-in-seconds-pattern-required": "Period in seconds pattern is required", + "min-period-seconds-message": "Only 1 second minimum period is allowed.", + "originator": "Originator", + "message-body": "Message body", + "message-metadata": "Message metadata", + "generate": "Generate", + "current-rule-node": "Current Rule Node", + "current-tenant": "Current Tenant", + "generator-function": "Generator function", + "test-generator-function": "Test generator function", + "generator": "Generator", + "test-filter-function": "Test filter function", + "test-switch-function": "Test switch function", + "test-transformer-function": "Test transformer function", + "transformer": "Transformer", + "alarm-create-condition": "Alarm create condition", + "test-condition-function": "Test condition function", + "alarm-clear-condition": "Alarm clear condition", + "alarm-details-builder": "Alarm details builder", + "test-details-function": "Test details function", + "alarm-type": "Alarm type", + "select-entity-types": "Select entity types", + "alarm-type-required": "Alarm type is required.", + "alarm-severity": "Alarm severity", + "alarm-severity-required": "Alarm severity is required", + "alarm-severity-pattern": "Alarm severity pattern", + "alarm-status-filter": "Alarm status filter", + "alarm-status-list-empty": "Alarm status list is empty", + "no-alarm-status-matching": "No alarm status matching were found.", + "propagate": "Propagate alarm to related entities", + "propagate-to-owner": "Propagate alarm to entity owner (Customer or Tenant)", + "propagate-to-tenant": "Propagate alarm to Tenant", + "condition": "Condition", + "details": "Details", + "to-string": "To string", + "test-to-string-function": "Test to string function", + "from-template": "From", + "from-template-required": "From is required", + "message-to-metadata": "Message to metadata", + "metadata-to-message": "Metadata to message", + "from-message": "From message", + "from-metadata": "From metadata", + "to-template": "To", + "to-template-required": "To Template is required", + "mail-address-list-template-hint": "Comma separated address list, use ${metadataKey} for value from metadata, $[messageKey] for value from message body", + "cc-template": "Cc", + "bcc-template": "Bcc", + "subject-template": "Subject", + "subject-template-required": "Subject Template is required", + "body-template": "Body", + "body-template-required": "Body Template is required", + "dynamic-mail-body-type": "Dynamic mail body type", + "mail-body-type": "Mail body type", + "body-type-template": "Body type template", + "reply-routing-configuration": "Reply Routing Configuration", + "rpc-reply-routing-configuration-hint": "These configuration parameters specify the metadata key names used to identify the service, session, and request for sending a reply back.", + "reply-routing-configuration-hint": "These configuration parameters specify the metadata key names used to identify the service and request for sending a reply back.", + "request-id-metadata-attribute": "Request Id", + "service-id-metadata-attribute": "Service Id", + "session-id-metadata-attribute": "Session Id", + "timeout-sec": "Timeout in seconds", + "timeout-required": "Timeout is required", + "min-timeout-message": "Only 0 minimum timeout value is allowed.", + "endpoint-url-pattern": "Endpoint URL pattern", + "endpoint-url-pattern-required": "Endpoint URL pattern is required", + "request-method": "Request method", + "use-simple-client-http-factory": "Use simple client HTTP factory", + "ignore-request-body": "Without request body", + "parse-to-plain-text": "Parse to plain text", + "parse-to-plain-text-hint": "If selected, request body message payload will be transformed from JSON string to plain text, e.g. msg = \"Hello,\\t\"world\"\" will be parsed to Hello, \"world\"", + "read-timeout": "Read timeout in millis", + "read-timeout-hint": "The value of 0 means an infinite timeout", + "max-parallel-requests-count": "Max number of parallel requests", + "max-parallel-requests-count-hint": "The value of 0 specifies no limit in parallel processing", + "max-response-size": "Max response size (in KB)", + "max-response-size-hint": "The maximum amount of memory allocated for buffering data when decoding or encoding HTTP messages, such as JSON or XML payloads", + "headers": "Headers", + "headers-hint": "Use ${metadataKey} for value from metadata, $[messageKey] for value from message body in header/value fields", + "header": "Header", + "header-required": "Header is required", + "value": "Value", + "value-required": "Value is required", + "topic-pattern": "Topic pattern", + "key-pattern": "Key pattern", + "key-pattern-hint": "Optional. If a valid partition number is specified, it will be used when sending the record. If no partition is specified, the key will be used instead. If neither is specified, a partition will be assigned in a round-robin fashion.", + "topic-pattern-required": "Topic pattern is required", + "topic": "Topic", + "topic-required": "Topic is required", + "bootstrap-servers": "Bootstrap servers", + "bootstrap-servers-required": "Bootstrap servers value is required", + "other-properties": "Other properties", + "key": "Key", + "key-required": "Key is required", + "retries": "Automatically retry times if fails", + "min-retries-message": "Only 0 minimum retries is allowed.", + "batch-size-bytes": "Produces batch size in bytes", + "min-batch-size-bytes-message": "Only 0 minimum batch size is allowed.", + "linger-ms": "Time to buffer locally (ms)", + "min-linger-ms-message": "Only 0 ms minimum value is allowed.", + "buffer-memory-bytes": "Client buffer max size in bytes", + "min-buffer-memory-message": "Only 0 minimum buffer size is allowed.", + "memory-buffer-size-range": "Memory buffer size must be between 0 and {{max}} KB", + "acks": "Number of acknowledgments", + "key-serializer": "Key serializer", + "key-serializer-required": "Key serializer is required", + "value-serializer": "Value serializer", + "value-serializer-required": "Value serializer is required", + "topic-arn-pattern": "Topic ARN pattern", + "topic-arn-pattern-required": "Topic ARN pattern is required", + "aws-access-key-id": "AWS Access Key ID", + "aws-access-key-id-required": "AWS Access Key ID is required", + "aws-secret-access-key": "AWS Secret Access Key", + "aws-secret-access-key-required": "AWS Secret Access Key is required", + "aws-region": "AWS Region", + "aws-region-required": "AWS Region is required", + "exchange-name-pattern": "Exchange name pattern", + "routing-key-pattern": "Routing key pattern", + "message-properties": "Message properties", + "host": "Host", + "host-required": "Host is required", + "port": "Port", + "port-required": "Port is required", + "port-range": "Port should be in a range from 1 to 65535.", + "virtual-host": "Virtual host", + "username": "Username", + "password": "Password", + "automatic-recovery": "Automatic recovery", + "connection-timeout-ms": "Connection timeout (ms)", + "min-connection-timeout-ms-message": "Only 0 ms minimum value is allowed.", + "handshake-timeout-ms": "Handshake timeout (ms)", + "min-handshake-timeout-ms-message": "Only 0 ms minimum value is allowed.", + "client-properties": "Client properties", + "queue-url-pattern": "Queue URL pattern", + "queue-url-pattern-required": "Queue URL pattern is required", + "delay-seconds": "Delay (seconds)", + "min-delay-seconds-message": "Only 0 seconds minimum value is allowed.", + "max-delay-seconds-message": "Only 900 seconds maximum value is allowed.", + "name": "Name", + "name-required": "Name is required", + "queue-type": "Queue type", + "sqs-queue-standard": "Standard", + "sqs-queue-fifo": "FIFO", + "gcp-project-id": "GCP project ID", + "gcp-project-id-required": "GCP project ID is required", + "gcp-service-account-key": "GCP service account key file", + "gcp-service-account-key-required": "GCP service account key file is required", + "pubsub-topic-name": "Topic name", + "pubsub-topic-name-required": "Topic name is required", + "message-attributes": "Message attributes", + "message-attributes-hint": "Use ${metadataKey} for value from metadata, $[messageKey] for value from message body in name/value fields", + "connect-timeout": "Connection timeout (sec)", + "connect-timeout-required": "Connection timeout is required.", + "connect-timeout-range": "Connection timeout should be in a range from 1 to 200.", + "client-id": "Client ID", + "client-id-hint": "Optional. Leave empty for auto-generated Client ID. Be careful when specifying the Client ID. Majority of the MQTT brokers will not allow multiple connections with the same Client ID. To connect to such brokers, your mqtt Client ID must be unique. When platform is running in a micro-services mode, the copy of rule node is launched in each micro-service. This will automatically lead to multiple mqtt clients with the same ID and may cause failures of the rule node. To avoid such failures enable \"Add Service ID as suffix to Client ID\" option below.", + "append-client-id-suffix": "Add Service ID as suffix to Client ID", + "client-id-suffix-hint": "Optional. Applied when \"Client ID\" specified explicitly. If selected then Service ID will be added to Client ID as a suffix. Helps to avoid failures when platform is running in a micro-services mode.", + "device-id": "Device ID", + "device-id-required": "Device ID is required.", + "clean-session": "Clean session", + "enable-ssl": "Enable SSL", + "credentials": "Credentials", + "credentials-type": "Credentials type", + "credentials-type-required": "Credentials type is required.", + "credentials-anonymous": "Anonymous", + "credentials-basic": "Basic", + "credentials-pem": "PEM", + "credentials-pem-hint": "At least Server CA certificate file or a pair of Client certificate and Client private key files are required", + "credentials-sas": "Shared Access Signature", + "sas-key": "SAS Key", + "sas-key-required": "SAS Key is required.", + "hostname": "Hostname", + "hostname-required": "Hostname is required.", + "azure-ca-cert": "CA certificate file", + "username-required": "Username is required.", + "password-required": "Password is required.", + "ca-cert": "Server CA certificate file", + "private-key": "Client private key file", + "cert": "Client certificate file", + "no-file": "No file selected.", + "drop-file": "Drop a file or click to select a file to upload.", + "private-key-password": "Private key password", + "use-system-smtp-settings": "Use system SMTP settings", + "use-metadata-dynamic-interval": "Use dynamic interval", + "metadata-dynamic-interval-hint": "Interval start and end input fields support templatization. Note that the substituted template value should be set in milliseconds. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.", + "use-metadata-interval-patterns-hint": "If selected, rule node use start and end interval patterns from message metadata or data assuming that intervals are in the milliseconds.", + "use-message-alarm-data": "Use message alarm data", + "overwrite-alarm-details": "Overwrite alarm details", + "use-alarm-severity-pattern": "Use alarm severity pattern", + "check-all-keys": "Check that all specified fields are present", + "check-all-keys-hint": "If selected, checks that all specified keys are present in the message data and metadata.", + "check-relation-to-specific-entity": "Check relation to specific entity", + "check-relation-to-specific-entity-tooltip": "If enabled, checks the presence of relation with a specific entity otherwise, checks the presence of relation with any entity. In both cases, relation lookup is based on configured direction and type.", + "check-relation-hint": "Checks existence of relation to specific entity or to any entity based on direction and relation type.", + "delete-relation-with-specific-entity": "Delete relation with specific entity", + "delete-relation-with-specific-entity-hint": "If enabled, will delete the relation with just one specific entity. Otherwise, the relation will be removed with all matching entities.", + "delete-relation-hint": "Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.", + "remove-current-relations": "Remove current relations", + "remove-current-relations-hint": "Removes current relations from the originator of the incoming message based on direction and type.", + "change-originator-to-related-entity": "Change originator to related entity", + "change-originator-to-related-entity-hint": "Used to process submitted message as a message from another entity.", + "start-interval": "Interval start", + "end-interval": "Interval end", + "start-interval-required": "Interval start is required.", + "end-interval-required": "Interval end is required.", + "smtp-protocol": "Protocol", + "smtp-host": "SMTP host", + "smtp-host-required": "SMTP host is required.", + "smtp-port": "SMTP port", + "smtp-port-required": "You must supply a smtp port.", + "smtp-port-range": "SMTP port should be in a range from 1 to 65535.", + "timeout-msec": "Timeout ms", + "min-timeout-msec-message": "Only 0 ms minimum value is allowed.", + "enter-username": "Enter username", + "enter-password": "Enter password", + "enable-tls": "Enable TLS", + "tls-version": "TLS version", + "enable-proxy": "Enable proxy", + "use-system-proxy-properties": "Use system proxy properties", + "proxy-host": "Proxy host", + "proxy-host-required": "Proxy host is required.", + "proxy-port": "Proxy port", + "proxy-port-required": "Proxy port is required.", + "proxy-port-range": "Proxy port should be in a range from 1 to 65535.", + "proxy-user": "Proxy user", + "proxy-password": "Proxy password", + "proxy-scheme": "Proxy scheme", + "numbers-to-template": "Phone Numbers To Template", + "numbers-to-template-required": "Phone Numbers To Template is required", + "numbers-to-template-hint": "Comma separated Phone Numbers, use ${metadataKey} for value from metadata, $[messageKey] for value from message body", + "sms-message-template": "SMS message Template", + "sms-message-template-required": "SMS message Template is required", + "use-system-sms-settings": "Use system SMS provider settings", + "min-period-0-seconds-message": "Only 0 second minimum period is allowed.", + "max-pending-messages": "Maximum pending messages", + "max-pending-messages-required": "Maximum pending messages is required.", + "max-pending-messages-range": "Maximum pending messages should be in a range from 1 to 100000.", + "originator-types-filter": "Originator types filter", + "interval-seconds": "Interval in seconds", + "interval-seconds-required": "Interval is required.", + "int-range": "Value must not exceed the maximum integer limit (2147483648)", + "min-interval-seconds-message": "Only 1 second minimum interval is allowed.", + "output-timeseries-key-prefix": "Output time series key prefix", + "output-timeseries-key-prefix-required": "Output time series key prefix required.", + "separator-hint": "You should press \"Enter\" to complete field input.", + "select-details": "Select details", + "entity-details-id": "Id", + "entity-details-title": "Title", + "entity-details-country": "Country", + "entity-details-state": "State", + "entity-details-city": "City", + "entity-details-zip": "Zip", + "entity-details-address": "Address", + "entity-details-address2": "Address2", + "entity-details-additional_info": "Additional Info", + "entity-details-phone": "Phone", + "entity-details-email": "Email", + "email-sender": "Email sender", + "fields-to-check": "Fields to check", + "add-detail": "Add detail", + "check-all-keys-tooltip": "If enabled, checks the presence of all fields listed in the message and metadata field names within the incoming message and its metadata.", + "fields-to-check-hint": "Press \"Enter\" to complete field name input. Multiple field names supported.", + "entity-details-list-empty": "At least one detail should be selected.", + "alarm-status": "Alarm status", + "alarm-required": "At least one alarm status should be selected.", + "no-entity-details-matching": "No entity details matching were found.", + "custom-table-name": "Custom table name", + "custom-table-name-required": "Table Name is required", + "custom-table-hint": "The table must be created in your Cassandra cluster and its name must start with the prefix 'cs_tb_' to avoid the data insertion to the common TB tables. Enter the table name here without the 'cs_tb_' prefix.", + "message-field": "Message field", + "message-field-required": "Message field is required.", + "table-col": "Table column", + "table-col-required": "Table column is required.", + "latitude-field-name": "Latitude field name", + "longitude-field-name": "Longitude field name", + "latitude-field-name-required": "Latitude field name is required.", + "longitude-field-name-required": "Longitude field name is required.", + "fetch-perimeter-info-from-metadata": "Fetch perimeter information from metadata", + "fetch-perimeter-info-from-metadata-tooltip": "If perimeter type is set to 'Polygon' the value of metadata field '{{perimeterKeyName}}' will be set as perimeter definition without additional parsing of the value. Otherwise, if perimeter type is set to 'Circle' the value of '{{perimeterKeyName}}' metadata field will be parsed to extract 'latitude', 'longitude', 'radius', 'radiusUnit' fields that uses for circle perimeter definition.", + "perimeter-key-name": "Perimeter key name", + "perimeter-key-name-hint": "Metadata field name that includes perimeter information.", + "perimeter-key-name-required": "Perimeter key name is required.", + "perimeter-circle": "Circle", + "perimeter-polygon": "Polygon", + "perimeter-type": "Perimeter type", + "circle-center-latitude": "Center latitude", + "circle-center-latitude-required": "Center latitude is required.", + "circle-center-longitude": "Center longitude", + "circle-center-longitude-required": "Center longitude is required.", + "range-unit-meter": "Meter", + "range-unit-kilometer": "Kilometer", + "range-unit-foot": "Foot", + "range-unit-mile": "Mile", + "range-unit-nautical-mile": "Nautical mile", + "range-units": "Range units", + "range-units-required": "Range units is required.", + "range": "Range", + "range-required": "Range is required.", + "polygon-definition": "Polygon definition", + "polygon-definition-required": "Polygon definition is required.", + "polygon-definition-hint": "Use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].", + "min-inside-duration": "Minimal inside duration", + "min-inside-duration-value-required": "Minimal inside duration is required", + "min-inside-duration-time-unit": "Minimal inside duration time unit", + "min-outside-duration": "Minimal outside duration", + "min-outside-duration-value-required": "Minimal outside duration is required", + "min-outside-duration-time-unit": "Minimal outside duration time unit", + "tell-failure-if-absent": "Tell Failure", + "tell-failure-if-absent-hint": "If at least one selected key doesn't exist the outbound message will report \"Failure\".", + "get-latest-value-with-ts": "Fetch timestamp for the latest telemetry values", + "get-latest-value-with-ts-hint": "If selected, the latest telemetry values will also include timestamp, e.g: \"temp\": \"{\"ts\":1574329385897, \"value\":42}\"", + "ignore-null-strings": "Ignore null strings", + "ignore-null-strings-hint": "If selected rule node will ignore entity fields with empty value.", + "add-metadata-key-values-as-kafka-headers": "Add Message metadata key-value pairs to Kafka record headers", + "add-metadata-key-values-as-kafka-headers-hint": "If selected, key-value pairs from message metadata will be added to the outgoing records headers as byte arrays with predefined charset encoding.", + "charset-encoding": "Charset encoding", + "charset-encoding-required": "Charset encoding is required.", + "charset-us-ascii": "US-ASCII", + "charset-iso-8859-1": "ISO-8859-1", + "charset-utf-8": "UTF-8", + "charset-utf-16be": "UTF-16BE", + "charset-utf-16le": "UTF-16LE", + "charset-utf-16": "UTF-16", + "select-queue-hint": "The queue name can be selected from a drop-down list or add a custom name.", + "device-profile-node-hint": "Useful if you have duration or repeating conditions to ensure continuity of alarm state evaluation.", + "persist-alarm-rules": "Persist state of alarm rules", + "persist-alarm-rules-hint": "If enabled, the rule node will store the state of processing to the database.", + "fetch-alarm-rules": "Fetch state of alarm rules", + "fetch-alarm-rules-hint": "If enabled, the rule node will restore the state of processing on initialization and ensure that alarms are raised even after server restarts. Otherwise, the state will be restored when the first message from the device arrives.", + "input-value-key": "Input value key", + "input-value-key-required": "Input value key is required.", + "output-value-key": "Output value key", + "output-value-key-required": "Output value key is required.", + "number-of-digits-after-floating-point": "Number of digits after floating point", + "number-of-digits-after-floating-point-range": "Number of digits after floating point should be in a range from 0 to 15.", + "failure-if-delta-negative": "Tell Failure if delta is negative", + "failure-if-delta-negative-tooltip": "Rule node forces failure of message processing if delta value is negative.", + "use-caching": "Use caching", + "use-caching-tooltip": "Rule node will cache the value of \"{{inputValueKey}}\" that arrives from the incoming message to improve performance. Note that the cache will not be updated if you modify the \"{{inputValueKey}}\" value elsewhere.", + "add-time-difference-between-readings": "Add the time difference between \"{{inputValueKey}}\" readings", + "add-time-difference-between-readings-tooltip": "If enabled, the rule node will add the \"{{periodValueKey}}\" to the outbound message.", + "period-value-key": "Period value key", + "period-value-key-required": "Period value key is required.", + "general-pattern-hint": "Use ${metadataKey} for value from metadata, $[messageKey] for value from message body.", + "alarm-severity-pattern-hint": "Use ${metadataKey} for value from metadata, $[messageKey] for value from message body. Alarm severity should be system (CRITICAL, MAJOR etc.)", + "output-node-name-hint": "The rule node name corresponds to the relation type of the output message, and it is used to forward messages to other rule nodes in the caller rule chain.", + "use-server-ts": "Use server timestamp", + "use-server-ts-hint": "Use the server’s current timestamp for time series data that lacks an explicit timestamp. This helps maintain proper ordering when processing messages from multiple sources or when messages arrive out of sequence.", + "kv-map-pattern-hint": "All input fields support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.", + "kv-map-single-pattern-hint": "Input field support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.", + "shared-scope": "Shared scope", + "server-scope": "Server scope", + "client-scope": "Client scope", + "attribute-type": "Attribute", + "attribute-type-description": "Fetch attribute value from database", + "attribute-type-result-description": "Store result as an entity attribute in the database", + "constant-type": "Constant", + "constant-type-description": "Define constant value", + "time-series-type": "Time series", + "time-series-type-description": "Fetch latest time-series value from database", + "time-series-type-result-description": "Store result as an entity time-series in the database", + "message-body-type": "Message", + "message-body-type-description": "Fetch argument value from incoming message", + "message-body-type-result-description": "Add result to the outgoing message", + "message-metadata-type": "Metadata", + "message-metadata-type-description": "Fetch argument value from incoming message metadata", + "message-metadata-result-description": "Add result to the outgoing message metadata", + "argument-tile": "Arguments", + "no-arguments-prompt": "No arguments configured", + "result-title": "Result", + "functions-field-input": "Functions", + "no-option-found": "No option found", + "argument-source-field-input": "Source", + "argument-source-field-input-required": "Argument source is required.", + "argument-key-field-input": "Key", + "argument-key-field-input-required": "Argument key is required.", + "constant-value-field-input": "Constant value", + "constant-value-field-input-required": "Constant value is required.", + "attribute-scope-field-input": "Attribute scope", + "attribute-scope-field-input-required": "Attribute scope os required.", + "default-value-field-input": "Default value", + "type-field-input": "Type", + "type-field-input-required": "Type is required.", + "key-field-input": "Key", + "add-entity-type": "Add entity type", + "add-device-profile": "Add device profile", + "key-field-input-required": "Key is required.", + "number-floating-point-field-input": "Number of digits after floating point", + "number-floating-point-field-input-hint": "Use 0 to convert result to integer", + "add-to-message-field-input": "Add to message", + "add-to-metadata-field-input": "Add to metadata", + "custom-expression-field-input": "Mathematical Expression", + "custom-expression-field-input-required": "Mathematical expression is required", + "custom-expression-field-input-hint": "Specify a mathematical expression to evaluate. Default expression demonstrates how to transform Fahrenheit to Celsius", + "retained-message": "Retained", + "attributes-mapping": "Attributes mapping", + "latest-telemetry-mapping": "Latest telemetry mapping", + "add-mapped-attribute-to": "Add mapped attributes to", + "add-mapped-latest-telemetry-to": "Add mapped latest telemetry to", + "add-mapped-fields-to": "Add mapped fields to", + "add-selected-details-to": "Add selected details to", + "clear-selected-types": "Clear selected types", + "clear-selected-details": "Clear selected details", + "clear-selected-fields": "Clear selected fields", + "clear-selected-keys": "Clear selected keys", + "geofence-configuration": "Geofence configuration", + "coordinate-field-names": "Coordinate field names", + "coordinate-field-hint": "Rule node tries to retrieve the specified fields from the message. If they are not present, it will look them up in the metadata.", + "presence-monitoring-strategy": "Presence monitoring strategy", + "presence-monitoring-strategy-on-first-message": "On first message", + "presence-monitoring-strategy-on-each-message": "On each message", + "presence-monitoring-strategy-on-first-message-hint": "Reports presence status 'Inside' or 'Outside' on the first message after the configured minimal duration has passed since previous presence status 'Entered' or 'Left' update.", + "presence-monitoring-strategy-on-each-message-hint": "Reports presence status 'Inside' or 'Outside' on each message after presence status 'Entered' or 'Left' update.", + "fetch-credentials-to": "Fetch credentials to", + "add-originator-attributes-to": "Add originator attributes to", + "originator-attributes": "Originator attributes", + "fetch-latest-telemetry-with-timestamp": "Fetch latest telemetry with timestamp", + "fetch-latest-telemetry-with-timestamp-tooltip": "If selected, latest telemetry values will be added to the outbound metadata with timestamp, e.g: \"{{latestTsKeyName}}\": \"{\"ts\":1574329385897, \"value\":42}\"", + "tell-failure": "Tell failure if any of the attributes are missing", + "tell-failure-tooltip": "If at least one selected key doesn't exist the outbound message will report \"Failure\".", + "created-time": "Created time", + "chip-help": "Press 'Enter' to complete {{inputName}} input. \nPress 'Backspace' to delete {{inputName}}. \nMultiple values supported.", + "detail": "detail", + "field-name": "field name", + "device-profile": "device profile", + "entity-type": "entity type", + "message-type": "message type", + "timeseries-key": "time series key", + "type": "Type", + "first-name": "First name", + "last-name": "Last name", + "label": "Label", + "originator-fields-mapping": "Originator fields mapping", + "add-mapped-originator-fields-to": "Add mapped originator fields to", + "fields": "Fields", + "skip-empty-fields": "Skip empty fields", + "skip-empty-fields-tooltip": "Fields with empty values will not be added to the output message/output metadata.", + "fetch-interval": "Fetch interval", + "fetch-strategy": "Fetch strategy", + "fetch-timeseries-from-to": "Fetch time series from {{startInterval}} {{startIntervalTimeUnit}} ago to {{endInterval}} {{endIntervalTimeUnit}} ago.", + "fetch-timeseries-from-to-invalid": "Fetch time series invalid (\"Interval start\" should be less than \"Interval end\").", + "use-metadata-dynamic-interval-tooltip": "If selected, the rule node will use dynamic interval start and end based on the message and metadata patterns.", + "all-mode-hint": "If selected fetch mode \"All\" rule node will retrieve telemetry from the fetch interval with configurable query parameters.", + "first-mode-hint": "If selected fetch mode \"First\" rule node will retrieve the closest telemetry to the fetch interval's start.", + "last-mode-hint": "If selected fetch mode \"Last\" rule node will retrieve the closest telemetry to the fetch interval's end.", + "ascending": "Ascending", + "descending": "Descending", + "min": "Min", + "max": "Max", + "average": "Average", + "sum": "Sum", + "count": "Count", + "none": "None", + "last-level-relation-tooltip": "If selected, the rule node will search related entities only on the level set in the max relation level.", + "last-level-device-relation-tooltip": "If selected, the rule node will search related devices only on the level set in the max relation level.", + "data-to-fetch": "Data to fetch", + "mapping-of-customers": "Mapping of customer's", + "map-fields-required": "All mapping fields are required.", + "attributes": "Attributes", + "related-device-attributes": "Related device attributes", + "add-selected-attributes-to": "Add selected attributes to", + "device-profiles": "Device profiles", + "mapping-of-tenant": "Mapping of tenant's", + "add-attribute-key": "Add attribute key", + "message-template": "Message template", + "message-template-required": "Message template is required", + "use-system-slack-settings": "Use system slack settings", + "slack-api-token": "Slack API token", + "slack-api-token-required": "Slack API token is required", + "keys-mapping": "keys mapping", + "add-key": "Add key", + "recipients": "Recipients", + "message-subject-and-content": "Message subject and content", + "template-rules-hint": "Both input fields support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the message metadata.", + "originator-customer-desc": "Use customer of incoming message originator as new originator.", + "originator-tenant-desc": "Use current tenant as new originator.", + "originator-related-entity-desc": "Use related entity as new originator. Lookup based on configured relation type and direction.", + "originator-alarm-originator-desc": "Use alarm originator as new originator. Only if incoming message originator is alarm entity.", + "originator-entity-by-name-pattern-desc": "Use entity fetched from DB as new originator. Lookup based on entity type and specified name pattern.", + "email-from-template-hint": "Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.", + "recipients-block-main-hint": "Comma-separated address list. All input fields support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.", + "forward-msg-default-rule-chain": "Forward message to the originator's default rule chain", + "forward-msg-default-rule-chain-tooltip": "If enabled, message will be forwarded to the originator's default rule chain, or rule chain from configuration, if originator has no default rule chain defined in the entity profile.", + "exclude-zero-deltas": "Exclude zero deltas from outbound message", + "exclude-zero-deltas-hint": "If enabled, the \"{{outputValueKey}}\" output key will be added to the outbound message if its value is not zero.", + "exclude-zero-deltas-time-difference-hint": "If enabled, the \"{{outputValueKey}}\" and \"{{periodValueKey}}\" output keys will be added to the outbound message only if the \"{{outputValueKey}}\" value is not zero.", + "search-direction-from": "From originator to target entity", + "search-direction-to": "From target entity to originator", + "del-relation-direction-from": "From originator", + "del-relation-direction-to": "To originator", + "target-entity": "Target entity", + "function-configuration": "Function configuration", + "function-name": "Function name", + "function-name-required": "Function name is required.", + "qualifier": "Qualifier", + "qualifier-hint": "If the qualifier is not specified, the default qualifier \"$LATEST\" will be used.", + "aws-credentials": "AWS Credentials", + "connection-timeout": "Connection timeout", + "connection-timeout-required": "Connection timeout is required.", + "connection-timeout-min": "Min connection timeout is 0.", + "connection-timeout-hint": "The amount of time to wait in seconds when initially establishing a connection before giving up and timing out. A value of 0 means infinity, and is not recommended.", + "request-timeout": "Request timeout", + "request-timeout-required": "Request timeout is required", + "request-timeout-min": "Min request timeout is 0", + "request-timeout-hint": "The amount of time to wait in seconds for the request to complete before giving up and timing out. A value of 0 means infinity, and is not recommended.", + "units": "Units", + "tell-failure-aws-lambda": "Tell Failure if AWS Lambda function execution raises exception", + "tell-failure-aws-lambda-hint": "Rule node forces failure of message processing if AWS Lambda function execution raises exception.", + "basic-mode": "Basic", + "advanced-mode": "Advanced", + "save-time-series": { + "processing-settings": "Processing settings", + "processing-settings-hint": "Define how incoming messages are processed. In Basic mode, select a preconfigured processing strategy or enable only WebSocket updates. Advanced mode allows you to select individual processing strategies for each action.", + "advanced-settings-hint": "Be cautious when configuring processing strategies. Certain combinations can lead to unexpected behavior.", + "strategy": "Strategy", + "deduplication-interval": "Deduplication interval", + "deduplication-interval-required": "Deduplication interval is required", + "deduplication-interval-min-max-range": "Deduplication interval should be at least 1 second and at most 1 day", + "strategy-type": { + "every-message": "On every message", + "skip": "Skip", + "deduplicate": "Deduplicate", + "web-sockets-only": "WebSockets only" + }, + "time-series": "Time series", + "latest": "Latest values", + "web-sockets": "WebSockets" + }, + "key-val": { + "key": "Key", + "value": "Value", + "see-examples": "See examples.", + "remove-entry": "Remove entry", + "remove-mapping-entry": "Remove mapping entry", + "add-mapping-entry": "Add mapping", + "add-entry": "Add entry", + "copy-key-values-from": "Copy key-values from", + "delete-key-values": "Delete key-values", + "delete-key-values-from": "Delete key-values from", + "at-least-one-key-error": "At least one key should be selected.", + "unique-key-value-pair-error": "'{{keyText}}' must be different from the '{{valText}}'!" + }, + "mail-body-types": { + "plain-text": "Plain text", + "html": "HTML", + "dynamic": "Dynamic", + "use-body-type-template": "Use body type template", + "plain-text-description": "Simple, unformatted text with no special styling or formating.", + "html-text-description": "Allows you to use HTML tags for formatting, links and images in your mai body.", + "dynamic-text-description": "Allows to use Plain Text or HTML body type dynamically based on templatization feature.", + "after-template-evaluation-hint": "After template evaluation value should be true for HTML, and false for Plain text." + } + }, "timezone": { "timezone": "Time zone", "select-timezone": "Select time zone", @@ -5804,6 +6622,8 @@ "layout-outlined-icon": "Outlined.Icon", "main": "Main", "background": "Background", + "button-icon-on": "Button icon 'On'", + "button-icon-off": "Button icon 'Off'", "power-on-colors": "Power 'On' colors", "power-off-colors": "Power 'Off' colors", "disabled-colors": "Disabled colors", @@ -5856,6 +6676,41 @@ "preview": "Preview", "copy-style-from": "Copy style from" }, + "value-stepper": { + "behavior": "Behavior", + "simplified": "Simplified", + "filled": "Filled", + "outlined": "Outlined", + "volume": "Volume", + "initial-state": "Initial state", + "initial-state-hint": "Action to get the initial value.", + "disabled-state": "Disabled state", + "disabled-state-hint": "Configure condition under which the component is disabled.", + "right-button-click": "Right button click", + "right-button-click-hint": "Action while pressing on right button.", + "left-button-click": "Left button click", + "left-button-click-hint": "Action while pressing on left button.", + "auto-scale": "Auto scale", + "value-range": "Range", + "min-range": "Min", + "max-range": "Max", + "value-increment-decrement-step": "Value increment/decrement step", + "value": "Value", + "value-box-background": "Value box background", + "border": "Border", + "button-appearance": "Button appearance", + "left": "Left", + "right": "Right", + "left-button": "Left button", + "right-button": "Right button", + "icon": "Icon", + "color-palette": "Color palette", + "main": "Main", + "background": "Background", + "button-icon-on": "Button icon 'On'", + "button-on-colors": "Power 'On' colors", + "disabled-colors": "Disabled colors" + }, "button-state": { "activated-state": "Activated state", "activated-state-hint": "Configure condition under which the button is active.", @@ -6605,6 +7460,7 @@ "datakey-value-type-date": "Date", "datakey-value-type-time": "Time", "datakey-value-type-select": "Select", + "datakey-value-type-radio": "Radio", "datakey-value-type-color": "Color", "value-is-required": "Value is required", "ability-to-edit-attribute": "Ability to edit attribute", @@ -6645,7 +7501,16 @@ "set-value-function": "setValue function", "json-invalid": "JSON value has an invalid format", "title": "Title", - "cancel-button-label": "'Cancel' button label" + "cancel-button-label": "'Cancel' button label", + "radio-button-settings": "Radio button settings", + "color": "Color", + "columns": "Columns", + "radio-options": "Radio options", + "no-radio-options": "No radio options configured", + "add-radio-option": "Add radio option", + "radio-label-position": "Label position", + "radio-label-position-before": "Before", + "radio-label-position-after": "After" }, "invalid-qr-code-text": "Invalid input text for QR code. Input should have a string type", "qr-code": { @@ -7529,15 +8394,18 @@ "set-attribute": "Set attribute", "get-time-series": "Get time series", "get-alarm-status": "Get alarm status", - "get-dashboard-state": "Get dashboard state", + "get-dashboard-state": "Get dashboard state id", + "get-dashboard-state-object": "Get dashboard state object", "add-time-series": "Add time series", "execute-rpc-text": "Execute RPC method '{{methodName}}'", "get-time-series-text": "Use time series '{{key}}'", "get-attribute-text": "Use attribute '{{key}}'", "get-alarm-status-text": "Use alarm status", "get-dashboard-state-text": "Use dashboard state", - "when-dashboard-state-is-text": "When dashboard state is '{{state}}'", - "when-dashboard-state-function-is-text": "When f(dashboard state) is '{{state}}'", + "get-dashboard-state-object-text": "Use dashboard state object", + "when-dashboard-state-is-text": "When dashboard state id is '{{state}}'", + "when-dashboard-state-function-is-text": "When f(dashboard state id) is '{{state}}'", + "when-dashboard-state-object-function-is-text": "When f(dashboard state object) is '{{state}}'", "set-attribute-to-value-text": "Set '{{key}}' attribute to: {{value}}", "add-time-series-value-text": "Add '{{key}}' time series value: {{value}}", "set-attribute-text": "Set '{{key}}' attribute", diff --git a/ui-ngx/src/assets/widget/value-stepper/filled.svg b/ui-ngx/src/assets/widget/value-stepper/filled.svg new file mode 100644 index 0000000000..26c94b92cd --- /dev/null +++ b/ui-ngx/src/assets/widget/value-stepper/filled.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui-ngx/src/assets/widget/value-stepper/simplified.svg b/ui-ngx/src/assets/widget/value-stepper/simplified.svg new file mode 100644 index 0000000000..8a71ad47c8 --- /dev/null +++ b/ui-ngx/src/assets/widget/value-stepper/simplified.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui-ngx/src/assets/widget/value-stepper/volume.svg b/ui-ngx/src/assets/widget/value-stepper/volume.svg new file mode 100644 index 0000000000..96bda9fa7a --- /dev/null +++ b/ui-ngx/src/assets/widget/value-stepper/volume.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index 0ae2b9584b..c73a4fc9b4 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -398,6 +398,9 @@ border-color: rgba(0, 0, 0, 0.12); } } + .mdc-notched-outline__notch { + border-left: none; + } .mdc-floating-label { top: 20px; font-weight: 400; diff --git a/ui-ngx/tailwind.config.js b/ui-ngx/tailwind.config.js index e53b102710..b71b4eb235 100644 --- a/ui-ngx/tailwind.config.js +++ b/ui-ngx/tailwind.config.js @@ -93,6 +93,7 @@ module.exports = { '0.75': '0.1875rem', '1.25': '0.3125rem', '3.75': '0.9375rem', + '5.5': '1.375rem', '6.25': '1.5625rem' }, minHeight: { @@ -152,6 +153,7 @@ module.exports = { '7.5': '1.875rem', '25': '6.25rem', '37.5': '9.375rem', + '50': '12.5rem', '62.5': '15.625rem', '72.5': '18.125rem' },